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に変えることになった」
なんていうのは、よく聞く話です。

 できる限り、サービスのインターフェイスは安定しているべきです。
もし下位レイヤーのモジュールがインターフェイスを持っていた場合、下位レイヤーに変更が入るということは、インターフェイスもリコンパイルされるということです。
ということは、このインターフェイスに依存している上位レイヤーのモジュールもリコンパイルを余儀なくされます。
このように、変更の余波が拡大することを防ぐために、頻繁に変更が入る下位レイヤーでなく、あまり変更が入らない上位レイヤーにインターフェイスを置くのです。

オススメ

 ソフトウェア開発者の方向けのオブジェクト指向の原則の解説は、アジャイルソフトウェア開発の奥義がオススメです。

 表紙に「原則・デザインパターンプラクティス完全統合」と銘打ってあるとおり、さまざまな知識を有機的に関連づけ、活用する方法がわかります。

 実際のソフトウェア開発の経過をなぞるカタチで、実践例を見せてくれるケーススタディで、より具体的に理解することができます。

posted by craftsman at 16:52 | 東京 ☔ | Comment(0) | TrackBack(0) | 原則
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント: [必須入力]

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。

この記事へのトラックバック
×

この広告は1年以上新しい記事の投稿がないブログに表示されております。