依存関係逆転の原則について勉強してみた

# 概要
SOLID原則について調査したシリーズです  
今回はDependency inversion principle（依存関係逆転の原則）について調査してみました  
※以降、依存関係逆転の原則と記載します

# 本題
## 2つの原則
以下2つの原則があります
1. 上位レベルのモジュールは下位レベルのモジュールに依存してはならない。両方とも抽象に依存すべきである
2. 抽象は詳細に依存してはならない。詳細が抽象に依存すべきである

とはいっても何のことかわからないので、言葉を分解して1原則づつ理解していきたいと思います

### 言葉を分解して説明
- 上位モジュール: 下位モジュールを使う側(依存する側)
- 下位モジュール: 上位モジュールから使われる側(依存される側)
- 抽象: 具体的な処理の内容を記載しない。アブストラクトメソッドや抽象クラスのことを指す
- 詳細: 具体的な処理内容

## 2つの原則を実現する
### Step1: 上位モジュールと下位モジュールが抽象に依存するようにする
#### 図を用いた抽象的な説明
あなたは八百屋の店主です  
品目ごとに異なるファーマーに依頼をしています    
キャベツは特定のAファーマーを指定しています  
Aファーマーは自身でキャベツを店まで運んでくれます  
![依存問題_抽象例_1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1952885/212f552b-134e-4849-a87d-f7eab382832b.png)  

  
しかし、Aファーマーが諸事情により使用できなくなりました  
そこで、別のファーマーにキャベツを依頼することになりましたが、そのファーマーは支払い通貨も異なりますし、商品を自身では届けてくれません  
![依存問題_抽象例_2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1952885/a529f2ab-09b3-4290-be25-68fd2dbf008c.png)  


つまり、**キャベツを注文する**というタスクがAファーマーにかなり依存していることが分かります
  
これを解決するためには、特定のファーマーに依存しないように**発注する**というタスクを**抽象化**する必要があります  
抽象化という言葉を代行という言葉に変換して説明します  
今までは発注タスクの内容がファーマーごとに異なっていたので、中間に発注を代行する企業を入れます  
この企業は複数のファーマーと連携しており、八百屋から特定のフォーマット(野菜の種類、品種、数量)で注文を受けると連携しているファーマーから商品を購入して、発注者まで郵送します  
すると、八百屋から見ると発注というタスクが特定のファーマーに依存せず、注文から商品の受け取りまでが完了するのです  
しかも、これはキャベツという特定の野菜だけではなく、他の野菜にも適用できます  
これが**発注タスクの抽象化**です  
![依存問題_抽象例_3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1952885/0bf0cd9d-46a8-426a-94dc-328ccf980db2.png)  

#### 具体例
ユーザー情報をDBに保存する場合を例に考えます  
##### 依存関係逆転の原則に反している場合
```python:database.py
from typing import TypedDict

class User(TypedDict):
    id: str
    name: str
    age: int

class DataBase:
    def save_user(user: User):
        print(f"save {user.name} in DataBase")
```
  
```python:bad_example.py
from database import DataBase, User

class UserService:
    def __init__(self, db: DataBase):
        self.db = db

    def create_user(self, user: User):
        target_user = {"id" user.id, "name": user.name.lower(), "age": user.age} # dbに保存するために処理をする
        self.db.save_user(save_user)

if __name__ == "__main__":
    db = DataBase()
    user_service = UserService(db)
    user = {"id": "Hycjd4","name": "tom", age: 16}
    user_service.create_user(user)
```
- UserServiceがDataBaseに依存している
    - DataBaseに合わせてUserService内で処理をしている
- DataBaseが変更(MySQLからPostgreSQL)するとcreate_user関数を書き直す必要がある

##### 依存関係逆転の原則に適応できている場合
```python:database.py
from abc import ABC, abstractmethod
from typing import TypedDict

class User(TypedDict):
    id: str
    name: str
    age: int

# UserServiceもMySQLDataBaseも抽象クラスに依存するようになる
class DataBaseAbstract(ABC)
    @abstractmethod
    def save_user(self, user: User):
        pass

class MySQLDataBase(DataBaseAbstract):
    def save_user(self, user: User):
        target_user = {"id" user.id, "name": user.name.lower(), "age": user.age}
        print(f"save {user.name} in MySQL DataBase")
```
  
```python:good_example.py
from database import MySQLDataBase, User

class UserService:
    def __init__(self, db: DataBase):
        self.db = db

    def create_user(self, user: User):
        self.db.save_user(save_user)

if __name__ == "__main__":
    db = MySQLDataBase()
    user_service = UserService(db)
    user = {"id": "Hycjd4","name": "tom", age: 16}
    user_service.create_user(user)
```

### Step2: 上位モジュールが下位モジュールに依存しているのを断ち切る
#### 図を用いた抽象的な説明
Step1のままだと、まだ発注タスクが発注会社に依存してしまっている状態です  
![依存を断ち切る_抽象例_1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1952885/66dcd749-c43d-4d5a-99af-90049a126bce.png)  

上位モジュールも下位モジュールも抽象に依存するようにするにはどうすればいいのでしょうか?  
そこで、野菜の売買を斡旋している団体があると家庭しましょう  
その団体に入っていれば買い手(上位モジュール)は団体を通して必要な野菜を購入することができます  
また売り手(下位モジュール)も加盟して納品していれば、買い手を探す必要もなく、決まった場所に納品すればいいだけになります  
![依存を断ち切る_抽象例2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1952885/43612950-d66e-4e9c-af1a-52cdefb2482b.png)  

すると、上位モジュールは自身が加盟している団体を通して野菜を購入しているため、下位モジュールの農家に依存することがなくなっています  
また、下位モジュールの農家からすると、団体に依存する形で野菜を売ることになるのです  
つまり、上位モジュールと下位モジュールが抽象である団体を通じて繋がっているのです  

#### 具体例　
```python:database.py
from good_example import User, DataBaseAbstract

class MySQLDataBase(DataBaseAbstract):
    def save_user(self, user: User):
        target_user = {"id" user.id, "name": user.name.lower(), "age": user.age}
        print(f"save {user.name} in MySQL DataBase")
```
  
```python:good_example.py
from abc import ABC, abstractmethod
from typing import TypedDict
from database import MySQLDataBase, User

# UserとDataBaseAbstractを上位モジュールに移動する
# 下位モジュール(database.py)が上位モジュールのDataBaseAbstractに依存するようになる

class User(TypedDict):
    id: str
    name: str
    age: int

class DataBaseAbstract(ABC)
    @abstractmethod
    def save_user(self, user: User):
        pass

class UserService:
    def __init__(self, db: DataBase):
        self.db = db

    def create_user(self, user: User):
        self.db.save_user(save_user)

if __name__ == "__main__":
    db = MySQLDataBase()
    user_service = UserService(db)
    user = {"id": "Hycjd4","name": "tom", age: 16}
    user_service.create_user(user)
```


# 参考サイト
- [現実世界の事象から学ぶSOLID原則](https://speakerdeck.com/h0r15h0/xian-shi-shi-jie-noshi-xiang-karaxue-busolidyuan-ze?slide=38)
- [SOLID原則完全に理解した！になるための本](https://zenn.dev/nakurei/books/solid-principle-kanzen-rikai/viewer/dependency-inversion-principle)