Pythonの型アノテーションを使ってInterfaceクラスをそれっぽく扱ってみる

### 概要
- [依存関係逆転の原則(DIP)](https://ja.wikipedia.org/wiki/%E4%BE%9D%E5%AD%98%E6%80%A7%E9%80%86%E8%BB%A2%E3%81%AE%E5%8E%9F%E5%89%87)等の場面、つまり、抽象に依存した実装を行いたい場合に、Pythonでそれっぽく実装してみる
    - Python3.5から利用可能な[TypeHints](https://www.python.org/dev/peps/pep-0484/)の型アノテーションを利用してみる。
    - Pythonは言語機能としてInterfaceがないため、```abc```ライブラリを使う。
    - 型アノテーションは型が違っていても怒らないので、あくまでそれっぽく書くというだけ。。。

---
### 前提
- 環境
    - python: v.3.6.8

---
### 作ってみるもの
- animalモジュール
    - `Animal`というInterfaceクラスと、それを継承した`Cat`クラスを実装する。
    - `Animal`クラスは`cry()`methodを持つ。
    - `Cat`などの具象クラスでは、`cry()`methodにて、それぞれの具象化された動物泣き声をprintする。というポリモーフィズムを説明する際に出てくるヤツです。
    - `Dog`クラスも用意するが、このクラスは`Animal`を継承しない。
- myclassモジュール
    - `Animal`の具象クラスを受け取り、`cry()`methodを叩く`MyClass`クラスを実装する。ここで`MyClass`では
        1. `Cat`クラスに依存しないようにしたい。（具象クラスの修正や、別の具象クラスが渡ってきても影響を受けないようにしたい
        2. `Animal`というInterfaceクラスを継承していることは知っておきたい
- mainモジュール
    - `Cat`や`Dog`クラスを具象化し、`MyClass`のmethodに渡す。

---
### animalモジュールを用意
#### Interfaceクラスを用意
- ```abc```ライブラリを使って、継承クラスにてInterfaceクラスを作ってみる

```python
class Animal(object, metaclass=abc.ABCMeta):
    """動物を表すInterfaceクラス。
    """
    @abc.abstractmethod
    def cry(self):
        """動物の鳴き声のInterface
        """
        pass # 具体的な実装はなし
```

#### 具象クラスを用意
- `Animal`クラスを継承して具象クラスを作ってみる

```python
class Cat(Animal):
    """猫を表すクラス
    Args:
        Animal: インターフェースクラス
    """

    def cry(self):
        """猫の鳴き声の具象method
        """
        print('meow')
```

- `Animal`クラスと`Cat`クラスの動きを確認してみる
    - `Cat`クラスをインスタンス化して、`cry()`を実行してみる

    ```
>>> cat = Cat()
>>> cat.cry()
meow
    ````

    - Interfaceクラスをそのままインスタンス化しようとすると怒られる

    ```shell
    >>> animal = Animal()
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: Can't instantiate abstract class Animal with abstract methods cry
    ```

    - 具象クラスでmethodを実装しなかった場合も、怒られる

    ```shell
>>> class Cat(Animal):
...     """猫を表すクラス
...     Args:
...         Animal: インターフェースクラス
...     """
...     def nocry(self):
...         """検証用method
...         """
...         print('???')
... 
>>> cat = Cat()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class Cat with abstract methods cry
    ```

#### Interfaceクラスと関係ないクラスを実装してみる
- ぱっと見は似てるが、`Animal`クラスを継承して ***いない*** `Dog`クラスを作ってみる

```python
class Dog(object):
    """犬を表すクラス。インターフェースは継承してない
    """
    def cry(self):
        """犬の鳴き声の具象method
        """
        print('bow wow')
```

---
### myclassモジュールを用意
#### 具象クラスを受け取って、`cry()`methodを実行するクラスを用意する
- 型アノテーションを使って、引数が`Animal` Interfaceを継承したクラスであることを明示

```python
from animal import Animal

class MyClass(object):
    def run(self, animal: Animal) -> None:
        animal.cry()
```

---
###mainモジュールを用意
- 実際の具象クラスをインスタンス化し、処理はMyClassに委譲するロジックを用意

```python
from animal import Cat, Dog
from myclass import MyClass

myclass = MyClass()
cat = Cat()
dog = Dog()

myclass.run(cat)
myclass.run(dog)
```

- 結果は

```shell
$python main.py 
meow
bow wow
```
- やっぱり犬も吠えてるね(´･ω･`)

---
###考察
- 型アノテーションはあくまで注釈なので、Interfaceクラスの派生クラスでなくても、動いてしまう
- とりあえず、それっぽいことはできてる（意味があるかはおいておいて）
    - 簡単な実験のメモ程度の情報だけど何かしらの参考になれば幸いです。。。
    - 「複数人で実装するときに、Interfaceクラスに注目してね」くらいの意味は与えられるのかな？