身近な例で考えてみよう。 あなたの会社は家庭用コンセントにつなぐ電気製品を開発している。 たまたま開発現場に Panasonic製のコンセントがあったので、 その差し込み口の寸法に合わせてプラグを設計した。 しかしこの方法には問題がある。Panasonic製のコンセントには ぴったり合っていたとしても、他社製のコンセントに対してはどうだろうか?
+----------+
| | 依存
| 製品 +---------> [Panasonic製 コンセント]
| |
+----------+
このような状態は、製品が「Panasonic製コンセント」という具体的な部品に 依存 (dependency) している状態である。これは望ましくない。 日本の家庭用コンセントは、 JIS C 8303 という規格で定義されている。したがって、本来は この規格に合わせて設計すべきなのである。
+----------+
| | 依存
| 製品 +----------> [JIS C 8303]
| | ^
+----------+ |
| 依存
|
[Panasonic製コンセント] [TOSHIBA製コンセント]
これが 依存性逆転の原則 (Dependency Inversion Principle) である。 ここでは、製品は (Panasonicという) 具体的なコンセントの実体ではなく、 抽象的な「規格」に依存している。 ここで「逆転」しているのは何かというと、いまや Panasonicのコンセントもこの規格に「依存」しているためである。 さらに Panasonic製だけでなく、TOSHIBA製のコンセントもこの規格に依存している。 抽象的な規格をもとに設計することで、この製品は どのメーカー製のコンセントにも対応できることになり、利用価値 (再利用性) が高められる。 プログラミングにおいては、このような「規格」のことを interface (インターフェイス) と呼ぶ。
依存性逆転の原則と interface は、先に解説した 依存注入 (Dependency Injection) を実現する際に重要である。 ブラウザをテストする例を思い出してほしい:
テスト時:
fakeInternet = 偽物のインターネット
display = browser(keyboard, mouse, fakeInternet)
本番時:
realInternet = 本物のインターネット
display = browser(keyboard, mouse, realInternet)
ここで、関数 browser
は本物のインターネット realInternet
と
偽物のインターネット fakeInternet
のどちらも使えるようになっていなければならない。
もし関数 browser
がどちらか一方のオブジェクト「だけ」に
合わせて設計されていたとすると、もう片方を切り換えて使うことはできない。
ここで出てくるのが依存性逆転の原則である。まず抽象的な規格
abstractInternet
なるものを想定し、関数 browser
を
これに依存するように設計するのである:
+----------+
| | 依存
| browser +---------> [abstractInternet]
| | ^
+----------+ |
| 依存
|
+------------+ +------------+
|fakeInternet| |realInternet|
+------------+ +------------+
ここで、さらに fakeInternet
と realInternet
のどちらも
この規格に準拠するように設計しておけば、関数 browser
はどちらの
オブジェクトも同様に使えるようになり、依存注入が実現できる。
なお、ソフトウェア開発においては「規格に準拠する」という言葉は使わず 「interfaceを 実装する (implement)」という。
注意:
実際には、interface で規定できるものは非常に限られており、 これはただオブジェクトにつける目印のようなものにすぎない。 Java においては、interface を使うことで型チェックのエラーを防ぐことができる。 また、interface という用語は Java や C# での呼び方で、 Swift や Objective-C では protocol (プロトコル) と呼ばれている。