Robert C. Martin氏による有名なオブジェクト指向設計(OOD)の原則Principles of OODがあります。そのうちDIP:依存逆転原則はクラス同士の結合度をいかに減らすか、またはどちらに依存すべきかを判定する原則です。

  • 上位モジュールは下位モジュールに依存してはならない。両者は抽象に依存すべきである。
  • 抽象は詳細(抽象の実装クラス)に依存してはならない。詳細は抽象に依存すべきである。

以下はオリジナルの論文の結びの言葉です。

依存性逆転の原則は、オブジェクト指向技術で得られると言われる数多くの利益のうち、もっとも根幹にあたる部分に位置付けられます。この原則を適切に適用することは、再利 用可能なフレームワークを構築する上では不可欠なのです。また、変更に際して回復の早い(弾力的な)コードを書く上では決定的な重要性を持ちます。抽象とこまごまとしたものはそれぞれが分離さるので、コードはとてもメンテナンスしやすいものになります。

BEAR.SundayではこのDIPを最大限に重要と考え、コード全域に渡って適用しています。1

実装ではなく抽象に依存する

解説してる文章で最も短く簡潔なのがStrategic Choiceブログの説明です。

DIPを一言で説明すると「抽象に依存せよ」という経験則。
プログラムは具体的なクラスに依存してはいけない。
プログラム内の関係はすべて、抽象クラスかインターフェースで終結すべきである

BEAR.Sundayのクラスでは、以下のように依存は原則全て注入してもらう事を期待します。

    /**      * @Inject      */     public function setPrinter(OutputDevice $printer)     {         $this->printer = $printer;     }

ここで大事なのはタイプヒンティング(この場合OutputDevice)を抽象、つまりインターフェイスかまたは抽象クラスにして具象クラスをタイプヒントにしないことです。タイプヒンティングをエラーチェックのためのアサーションとだけ捉えず、DIPに従った設計のための実装として考えます。

アンチパターン考察

直接生成する

$service = new MyService;

$serviceがMyServiceのインスタンスであると静的束縛されています。

スタティックコール

MyService::doSomething();

これも実装に依存しています。

コンテナに依存する

$id = $this->container(‘serviceA’)->getB()->getC()->getId();
  • このコードでは抽象への依存はなくコンテナに入った実装に依存しています(DIP違反)
  • メソッドチェーンで繋がれた依存の依存が持っている知識を前提としていています(デメテルの法則違反
  • getterを使う事でidというオブジェクトの構成要素の暴露がされています。(カプセル化)

実装に依存してオブジェクトを探しまわるようなコードから、インターフェイスを通じて依存を受け取るコードに変更します。

Conclusion

Robert C. Martin氏が「オブジェクト指向技術で得られると言われる数多くの利益のうちもっとも根幹にあたる部分」というこのDIPですが、「抽象のタイプヒントを指定しましょう」というのはただの一例で、「抽象に依存せよ」というの適用範囲の大変広いソフトウエア品質に寄与する大原則だと思います。メソッドのアクセス権 (visibility) と同じく実装での機能というより設計指向の実装として、設計者をよりよい設計にガイドする制約だと考えます。