0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Posted at

概要

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

本題

2つの原則

以下2つの原則があります

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

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

言葉を分解して説明

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

2つの原則を実現する

Step1: 上位モジュールと下位モジュールが抽象に依存するようにする

図を用いた抽象的な説明

あなたは八百屋の店主です
品目ごとに異なるファーマーに依頼をしています
キャベツは特定のAファーマーを指定しています
Aファーマーは自身でキャベツを店まで運んでくれます
依存問題_抽象例_1.PNG

しかし、Aファーマーが諸事情により使用できなくなりました
そこで、別のファーマーにキャベツを依頼することになりましたが、そのファーマーは支払い通貨も異なりますし、商品を自身では届けてくれません
依存問題_抽象例_2.PNG

つまり、キャベツを注文するというタスクがAファーマーにかなり依存していることが分かります

これを解決するためには、特定のファーマーに依存しないように発注するというタスクを抽象化する必要があります
抽象化という言葉を代行という言葉に変換して説明します
今までは発注タスクの内容がファーマーごとに異なっていたので、中間に発注を代行する企業を入れます
この企業は複数のファーマーと連携しており、八百屋から特定のフォーマット(野菜の種類、品種、数量)で注文を受けると連携しているファーマーから商品を購入して、発注者まで郵送します
すると、八百屋から見ると発注というタスクが特定のファーマーに依存せず、注文から商品の受け取りまでが完了するのです
しかも、これはキャベツという特定の野菜だけではなく、他の野菜にも適用できます
これが発注タスクの抽象化です
依存問題_抽象例_3.PNG

具体例

ユーザー情報をDBに保存する場合を例に考えます

依存関係逆転の原則に反している場合
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")
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関数を書き直す必要がある
依存関係逆転の原則に適応できている場合
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")
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

上位モジュールも下位モジュールも抽象に依存するようにするにはどうすればいいのでしょうか?
そこで、野菜の売買を斡旋している団体があると家庭しましょう
その団体に入っていれば買い手(上位モジュール)は団体を通して必要な野菜を購入することができます
また売り手(下位モジュール)も加盟して納品していれば、買い手を探す必要もなく、決まった場所に納品すればいいだけになります
依存を断ち切る_抽象例2.PNG

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

具体例 

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")
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)

参考サイト

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?