2007-07-12

依存関係逆転の原則

 いよいよ、依存関係逆転の原則のお話です。

DI(Dependency Injection)やIoC(Inversion of Control)などで、巷で話題ですね。
SpringやSeasarなど、依存関係逆転の考え方を一歩進めた、依存性注入の機能を持った軽量コンテナのフレームワークがいくつも開発されています。

これらのフレームワークの意義を理解するためにも、ぜひとも依存性逆転の原則を理解しておきたいところです。

コンテンツ

  1. その依存、どっち向き?
  2. チェストの依存関係
  3. 会社の依存関係
  4. 実装にあてはめてみる
  5. オススメ

依存性逆転の原則は、英語ではDependency Inversion Principleといい、DIPと略されます。
依存性注入のDependency Injectionと間違えやすいので、お気をつけ下さい。
原則の方は、Dependency Inversionです。
依存関係逆転の原則は、以下のようなものです。
  1. 上位のモジュールは、下位のモジュールに依存してはならない。
    どちらのモジュールも「抽象」に依存すべきである。
  2. 「抽象」は実装の詳細に依存してはならない。
    実装の詳細が「抽象」に依存すべきである。

では、詳しく見ていきましょう。

その依存、どっち向き?

オブジェクトは互いに依存し合っているということは、基礎編基礎 第8回: オブジェクトの関連―依存でお話ししました。
依存には、方向というものがあります。
AオブジェクトとBオブジェクトが依存し合っているとします。
このとき、AオブジェクトがBオブジェクトに依存しているのか、それとも逆に、BオブジェクトがAオブジェクトに依存しているのか?
これが依存の方向です。

チェストの依存関係

私たちの身の回りにあるものは、たいてい細かいオブジェクトが大きなオブジェクトに依存しています。
たとえばチェスト。
チェストには枠と引き出しがありますね。
引き出しは、チェストの大きさによって3つくらいだったり、7つくらいだったりします。
これ、枠と引き出しの依存関係はどうなってるんでしょう?
枠が引き出しに依存してるんでしょうか?
引き出しが枠に依存してるんでしょうか?
先に枠があって、それにあわせて引き出しが作られたんでしょうかね?
とすると、引き出しが枠に依存しているといえそうですね。
んー、先に引き出しがあって、それに合わせて枠を作るってのは、なんか不自然なカンジですね。
とすると、やっぱり引き出しが枠に依存してるのかな。
と、ちょっと待ってください。
この問題、そもそものスタートから間違ってます。
誰も、なんにもなしでいきなりチェストの枠を作り出したり、引き出しを作ってみたりしませんよね。
もちろん、最初に設計図があるのです。
では、設計図の中でも、この問題の焦点である枠と引き出しの関係のところを見てみましょう。
すると、枠のどこにどういうサイズの引き出し穴があいているかが、書いてありますよね。
枠は、そういう穴を備えるように作られます。
引き出しは、そういう穴にあてはまるように作られます。
気がつきましたか?
引き出し穴というのは、空っぽの空間を指しますよね。
引き出し穴というのは、設計図には書いてあるけれど、実際には存在しない「抽象」的なものです。
そして、これがまさに枠と引き出しの間のインターフェイスです。
枠は引き出し穴というインターフェイスを持ち、それに依存しています。
引き出しも、引き出し穴というインターフェイスに依存しているのです。
最初にお話しした、この原則の内容と照らし合わせてみましょう。
  1. チェストの枠は、引き出しに依存してはならない。
    どちらも引き出し穴に依存すべきである。
  2. 引き出し穴は、枠や引き出しに依存してはならない。
    枠や引き出しが引き出し穴に依存すべきである。
もう一度。
  1. 上位のモジュール(チェストの枠)は、下位のモジュール(引き出し)に依存してはならない。
    どちら(のモジュール)も「抽象」(引き出し穴)に依存すべきである。
  2. 「抽象」(引き出し穴)は、実装の詳細(枠や引き出し)に依存してはならない。
    実装の詳細(枠や引き出し)が「抽象」(引き出し穴)に依存すべきである。
どうです?
チェストには枠と引き出しがあって、接し合っていますね。
だから、ぱっと見それらが密に関連し合い、依存し合っているように見えます。
でも、実際には依存し合ってないんです。
それらは、実際には設計図の上にしか存在しない、インターフェイスに依存しているのです。
ここで、冒頭で言った「細かいオブジェクトが大きなオブジェクトに依存している」ということを、もう少しちゃんと言い直してみましょう。
「細かいオブジェクトは、大きなオブジェクトが持っているインターフェイスに依存している」のです。
たとえば、料理と食材もそうですね。
レシピという、いわば料理の設計図というようなものに依存しているのです。
そういう目で見直してみると、多くのオブジェクト同士は、実際には直接依存し合っているわけではないことがわかります。

会社の依存関係

私たちは、普段このこと―オブジェクト同士は直接には依存し合わないということ―に、けっこう気がつかないものです。
たとえば、会社と社員の関係を見てみましょう。
あなたのお勤め先には、何人の方がいらっしゃいますか?
その方達の中には、普段漠然と会社に依存しているつもりでいらっしゃる方も多いのではないでしょうか?
あるいは逆に、自分がいるからこそ会社が成り立ってるんだ!というかんじで、会社が自分に依存している気でいらっしゃる方もおられるかもしれません。
オブジェクト指向の考え方でいえば、どちらも正しくありません。
会社には、「こいういう人材が何人いるべき」というインターフェイスがあります。
社員はそのインターフェイスに依存して、その会社に勤めているわけです。
会社も、そのインターフェイスに依存して運営されているのです。
もちろん、人間関係とか、そのへんはヌキにしての話ですけどね。

実装にあてはめてみる

原則の内容にある「上位のモジュール」「下位のモジュール」の上位/下位とは、レイヤー()のことを指します。
モジュールとは、実行ファイルだったり、サブシステムだったり、パッケージだったりです。
このいずれも、通常はレイヤー構造をなします。
下位のモジュールというのは、データベースアクセスやファイルシステムへのアクセス、他システムへのインターフェイスなどを直接行うモジュールだったり、ファシリティやユーティリティを提供するモジュールのことを言います。
つまり、より低レベルで細かい処理を行うモジュールです。
上位のモジュールというのは、より大きなサービス、概念を提供するモジュールです。
このようなレイヤーで見た場合、依存性を逆転させるためには、まず上位レイヤーが必要とするサービスをインターフェイスとして宣言します。
そして、下位のレイヤーはそのインターフェイスをインプリメントすることでサービスの実装を提供します。
これが、依存関係逆転の原則から見た理想の状態です。
このインターフェイスが、なぜ下位レイヤーでなく上位レイヤーに属する必要があるのでしょうか?
理由は2つあります。

主導権は上位レイヤーにあり
上位レイヤーは「自分が必要とするサービスはこれだ!」と宣言するためにインターフェイスを持ちます。
下位レイヤーは、それに従って実装を提供するのです。
考えてもみてください。
一般に、下位レイヤーの方が上位レイヤーより具体的で詳細な実装を受け持ちます。
OS やデータベースとのこまごまとしたやり取りや、消費税のような細かい計算などです。
それに対して、上位レイヤーにいけばいくほど、大きな概念を扱うことになります。
部署という概念だとか、人事という業務だとかです。
ちまちました細かいことを実行する下位レイヤーと、業務全体を司る上位レイヤー、どっちが主導権を握るべきですか?
もし下位レイヤーがインターフェイスを持っていたら、上位レイヤーがそれを実装して、従わなければなりません。
おかしいですよね。
主導権は上位レイヤーが握るべきなのです。
また、上位レイヤーがインターフェイスを持つことで、実際的なメリットがあります。
いくらでも下位レイヤーは交換がきくようになるということです。
そのインターフェイスを実装しているモジュールであれば、上位レイヤーはどれでも同じように扱うことができるのです。

変更の影響を防ぐ
普通、大きな概念というのは、あまり変更が入らないものです。
たとえば人事という業務そのものは、たいていの会社で共通のものであり、新しい人事のありかたのようなものが発明されない限り、変わるものではありません。
しかし、OS やデータベースは、いつ変更されてもおかしくないものです。
「今 Windows で動いてるアプリケーションを、今度 Linux に乗せかえることになった」
だとか、
「データベースを SQL Server から Oracle に変えることになった」
なんていうのは、よく聞く話です。
できる限り、サービスのインターフェイスは安定しているべきです。
もし下位レイヤーのモジュールがインターフェイスを持っていた場合、下位レイヤーに変更が入るということは、インターフェイスもリコンパイルされるということです。
ということは、このインターフェイスに依存している上位レイヤーのモジュールもリコンパイルを余儀なくされます。
このように、変更の余波が拡大することを防ぐために、頻繁に変更が入る下位レイヤーでなく、あまり変更が入らない上位レイヤーにインターフェイスを置くのです。

オススメ

原則の解説は、アジャイルソフトウェア開発の奥義がオススメです。
さまざまな知識を有機的に関連づけ、活用する方法がわかります。
第一版の表紙には、「原則・デザインパターン・プラクティス完全統合」と銘打ってありましたが、まさに知識の統合です。
実際のソフトウェア開発の経過をなぞるカタチで、実践例を見せてくれるケーススタディで、より具体的に理解することができます。
Top
2013.03.06
リンクと説明の端々を改訂しました。

2 件のコメント:

  1. 今まで読んだ依存関係逆転の原則の解説の中で一番わかりやすかった
    なぜ今までこの記事にたどり着かなかったのかが謎なくらい。

    返信削除
  2. ありがとうございます。
    コメントを付けていただいたので自分でも読み返してみましたが、これを書いてからもう 4 年も経ったのかとしみじみです。
    なにぶん昔のもので拙い文章ですが、いまだに読んでいただけて嬉しいかぎりです。

    返信削除

Related Posts Plugin for WordPress, Blogger...