依存関係逆転の原則
1. 原則
- 上位レベルのモジュールは下位レベルのモジュールに依存すべきではない。両方とも抽象(abstractions)に依存すべきである
- 下位レイヤで実装されるクラスは上位レイヤで用意されたインターフェイスを実装せよ
- 抽象化されたものに依存すべきであり、詳細に依存してはいけない
- 実装の詳細が、抽象に依存すべきである
2. 根拠
- 上位レイヤーが下位レイヤーに依存していると下位レイヤーの変更が上位レイヤーに伝播してしまう(そのリスクがある)
- 上位レイヤーは下位レイヤーの変更に伴う影響を受けるべきではない
- 不安定なモジュールが 依存されている と修正範囲が大きくなるため、 依存されない ようにしたい。
3. 指針
- 次のようなPythonコードを考える
-
layer1.py
とlayer2.py
はそれぞれレイヤーを表現しており、layer1.py
を上位レイヤー、layer2.py
を下位レイヤーとする
layer1.py
layer2.py
-
layer1.py
とlayer2.py
はそれぞれ以下のように実装されていると考える
# layer1.py
import layer2
class Program:
def exec():
logic = layer2.Logic()
return logic.execute()
# layer2.py
class Logic:
def execute():
return 'Layer2 Execute.'
- 上位レイヤー(
layer1.py
)で下位レイヤー(layer2.py
)のモジュールをインポートしており、なおかつ下位レイヤーのクラスを直接使用している
- この状況が「上位レイヤーが下位レイヤーに依存した状態」
- そこで、このプログラムを以下のように改造してみる
# layer1.py
class Logic(metaclass=ABCMeta):
@abstractmethod
def execute():
pass
class Program:
def __init__(self, logic):
self.logic = logic # <-- logic引数はLogicクラスのインスタンス
def exec():
return self.logic.execute()
# layer2.py
import layer1
class Logic(layer1.Logic):
def execute():
return 'Layer2 Execute.'
- 上位レイヤー側で
Logic
のインターフェイスを定義し、下位レイヤーで実装させることにした
- 上位レイヤー側にあったimport文が消え、下位レイヤーが上位レイヤーをimportするようになった
- 上位レイヤーの
Program
クラスは自分でLogic
クラスのインスタンスを生成せず、コンストラクタでLogic
クラスのインスタンスを受け取るようになった
- このようにする事で、上位レイヤーは下位レイヤーに全く依存しなくなった(
Program
クラスにLogic
のインスタンスを渡すのは利用者側の役割なのでここでは検討しない)
- この状況が「依存関係逆転の原則に則った状態」
4. 注意事項
コードの複雑度は上昇する
- 変更が発生しづらいコードでは適用する必要は薄い
- データアクセスやネットワークアクセスが発生するレイヤーを抽象化する為に利用するのが典型的な利用法
- 上位レイヤーが下位レイヤーに依存することで問題が発生することが明らかな場合のみ検討するべき
- 思考停止で全てに適用すべきものではない
関連
参考資料