概要
SOLID原則について調査したシリーズです
今回はDependency inversion principle(依存関係逆転の原則)について調査してみました
※以降、依存関係逆転の原則と記載します
本題
2つの原則
以下2つの原則があります
- 上位レベルのモジュールは下位レベルのモジュールに依存してはならない。両方とも抽象に依存すべきである
- 抽象は詳細に依存してはならない。詳細が抽象に依存すべきである
とはいっても何のことかわからないので、言葉を分解して1原則づつ理解していきたいと思います
言葉を分解して説明
- 上位モジュール: 下位モジュールを使う側(依存する側)
- 下位モジュール: 上位モジュールから使われる側(依存される側)
- 抽象: 具体的な処理の内容を記載しない。アブストラクトメソッドや抽象クラスのことを指す
- 詳細: 具体的な処理内容
2つの原則を実現する
Step1: 上位モジュールと下位モジュールが抽象に依存するようにする
図を用いた抽象的な説明
あなたは八百屋の店主です
品目ごとに異なるファーマーに依頼をしています
キャベツは特定のAファーマーを指定しています
Aファーマーは自身でキャベツを店まで運んでくれます
しかし、Aファーマーが諸事情により使用できなくなりました
そこで、別のファーマーにキャベツを依頼することになりましたが、そのファーマーは支払い通貨も異なりますし、商品を自身では届けてくれません
つまり、キャベツを注文するというタスクがAファーマーにかなり依存していることが分かります
これを解決するためには、特定のファーマーに依存しないように発注するというタスクを抽象化する必要があります
抽象化という言葉を代行という言葉に変換して説明します
今までは発注タスクの内容がファーマーごとに異なっていたので、中間に発注を代行する企業を入れます
この企業は複数のファーマーと連携しており、八百屋から特定のフォーマット(野菜の種類、品種、数量)で注文を受けると連携しているファーマーから商品を購入して、発注者まで郵送します
すると、八百屋から見ると発注というタスクが特定のファーマーに依存せず、注文から商品の受け取りまでが完了するのです
しかも、これはキャベツという特定の野菜だけではなく、他の野菜にも適用できます
これが発注タスクの抽象化です
具体例
ユーザー情報をDBに保存する場合を例に考えます
依存関係逆転の原則に反している場合
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")
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関数を書き直す必要がある
依存関係逆転の原則に適応できている場合
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")
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のままだと、まだ発注タスクが発注会社に依存してしまっている状態です
上位モジュールも下位モジュールも抽象に依存するようにするにはどうすればいいのでしょうか?
そこで、野菜の売買を斡旋している団体があると家庭しましょう
その団体に入っていれば買い手(上位モジュール)は団体を通して必要な野菜を購入することができます
また売り手(下位モジュール)も加盟して納品していれば、買い手を探す必要もなく、決まった場所に納品すればいいだけになります
すると、上位モジュールは自身が加盟している団体を通して野菜を購入しているため、下位モジュールの農家に依存することがなくなっています
また、下位モジュールの農家からすると、団体に依存する形で野菜を売ることになるのです
つまり、上位モジュールと下位モジュールが抽象である団体を通じて繋がっているのです
具体例
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")
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)