連載
.NETで始めるデザインパターン

第7回 デザインパターンの落とし穴

太陽システム株式会社 中西 庸文
Microsoft MVP 2005 - Solutions Architect)
2005/08/06
Page1 Page2

■保守性を考慮したパッケージ分割の指針

 それでは、このパッケージ分割の指針としての6つの設計原則を紹介しよう。これらの設計原則は第2回のGRASPパターンで説明した「凝集度(Cohesion)と結合度(Coupling)」を基に分類されている。高凝集性と疎結合性に注目することがオブジェクト指向設計の要であることを再認識していただきたい。

 以下に、『アジャイルソフトウェア開発の奥義』に記載されている、パッケージに関する6つの設計原則を紹介する。

分類 原則名 説明
パッケージ内部の凝集度(Cohesion)
1.
再利用・リリース等値の原則(REP:Reuse-Release Equivalency Principle) 再利用の単位とリリースの単位は等価になる
2.
全再利用の原則(CRP:Common Reuse Principle) パッケージに含まれるクラスは、すべて一緒に再利用される。つまり、パッケージに含まれるいずれかのクラスを再利用するということは、そのほかのクラスもすべて再利用することを意味する
3.
閉鎖性共通の原則(CCP:Common Closure Principle) パッケージに含まれるクラスは、みな同じ種類の変更に対して閉じているべきである。パッケージに影響する変更はパッケージ内のすべてのクラスに影響を及ぼすが、ほかのパッケージには影響しない
パッケージ同士の結合度(Coupling)
4.
非循環依存関係の原則(ADP:Acyclic Dependencies Principle) パッケージ依存グラフに循環を持ち込んではならない
5.
安定依存の原則(SDP:Stable Dependencies Principle) 安定する方向に依存せよ
6.
安定度・抽象度等価の原則(SAP:Stable Abstractions Principle) パッケージの抽象度と安定度は同程度でなければならない
パッケージに関する6つの設計原則

●パッケージ内部の凝集度(Cohesion)に関する設計原則

1. 再利用・リリース等値の原則(REP:Reuse-Release Equivalency Principle)

 再利用の単位(パッケージ)が、リリースの単位より小さくなってはいけない。パッケージに含まれる個々のクラス単位で再利用するのではなく、パッケージごと再利用できるようにすべきである。つまり、リリースはパッケージの単位で行う必要がある。また、パッケージを分割する際には、それを再利用する側の視点に立って個々のパッケージを構築すべきである。

2. 全再利用の原則(CRP:Common Reuse Principle)

 パッケージを利用する側の視点に立ってパッケージを構築すること。クライアントから利用されないクラスは、同一パッケージに含めてはならない。パッケージにクライアントが利用しないクラスが含まれていれば、クライアントには本来関係のないそれらのクラスの修正に伴ってパッケージが再リリースされることになる。これは先の「再利用・リリース等値の原則」に違反することにもなる。

3. 閉鎖性共通の原則(CCP:Common Closure Principle)

 この原則は、クラスに関する設計原則である「単一責任の原則」をパッケージに当てはめたものである。クラスの変更理由が1つでなければいけないように、パッケージの変更理由も複数存在してはいけない。変更理由が複数のパッケージにまたがってしまえば、保守性は低下する。つまりパッケージは変更理由が同じクラス群で構成されるべきなのである。こうすることでリリースの単位とパッケージの単位を同じにすることができる。

●パッケージ同士の結合度(Coupling)に関する設計原則

4. 非循環依存関係の原則(ADP:Acyclic Dependencies Principle)

 パッケージ間に依存の循環があれば、1つのパッケージ内のクラスに対する修正の影響が、関連するすべてのパッケージに及ぶ。パッケージをビルドする順番に制約があるような場合がよい例である。これではせっかくパッケージとして分割されていても、実際は分割されていないのと同じである。

 以下に、循環のないパッケージ構造と循環のあるパッケージ構造を示す。

循環のないパッケージ構造
 
循環のあるパッケージ構造

 パッケージ間の循環を断つ手段として次の2つの方法が存在する。

 1つ目の方法は、クラスに関する設計原則である「依存関係逆転の原則」を適用する方法だ。例えばパッケージMyGuiPartsのクラスXがパッケージMyApplicationのクラスYに依存しているような場合には、パッケージMyGuiParts内にクラスXが利用する抽象インターフェイス(以下の例ではIXServerインターフェイス)を配置し、その派生クラスとしてYクラスをパッケージMyApplication内に配置する。こうすることでパッケージMyGuiPartsとパッケージMyApplicationの依存関係が逆転して循環が断たれる。

クラス間の依存が原因で循環があるパッケージの関係
パッケージMyGuiPartsのクラスXが、パッケージMyApplicationのクラスYに依存している。このため、パッケージMyGuiPartsは、パッケージMyApplicationに依存してしまう。この依存関係のままでは、先ほどの図で示した循環のあるパッケージ構造となってしまう。
 
依存関係逆転の原則によって循環が断たれたパッケージの関係
パッケージMyGuiParts内にクラスXが利用する抽象インターフェイス(この例ではIXServerインターフェイス)を配置し、その派生クラスとしてYクラスをパッケージMyApplication内に配置する。これにより、パッケージMyGuiPartsのクラスXは、パッケージMyApplicationのIXServerインターフェイスに依存することになり、パッケージMyGuiPartsからパッケージMyApplicationへの依存は消失する。逆にパッケージMyApplicationのYクラスからIXServerインターフェイスへの依存が発生して、結果的に依存関係が逆転することになる。これにより、先ほどの図で示されたパッケージ構造の循環は解消する。

 もう1つの方法は新たなパッケージを追加する方法である。新たに追加したパッケージに依存元が参照しているクラスを移動させることで循環が断たれる。

新たなパッケージの追加によって循環が断たれたパッケージの関係
新たなパッケージMyUtilitiesを追加し、パッケージMyApplication内にあったクラスYをそのパッケージMyUtilities内に移動させる。これにより、MyGuiPartsからパッケージMyApplicationへの依存は消失することになり、先ほどの図で示されたパッケージ構造の循環も解消する。

5. 安定依存の原則(SDP:Stable Dependencies Principle)

 パッケージは、自分よりも不安定なパッケージに依存してはならない。変更が少ない安定したパッケージでも、依存先のパッケージが不安定であればその影響が及んで結局は不安定になってしまう。つまり、末端(依存されるクラスのない)のパッケージから依存関係を上にたどっていくに従って、パッケージの安定度が増すようにすべきである。

 また、システム中のパッケージすべてが安定している必要はない。先の「閉鎖性共通の原則」に従って構成されたパッケージは、変更理由が同じクラス群がまとめられている。いい換えれば、変更することを意識して作られた不安定なパッケージである。システムに柔軟性を持たせようとすれば、このような不安定なパッケージと安定したパッケージを混在させる必要がある。ただし、安定依存の原則に従うためには「依存関係逆転の原則」を使用してパッケージ間の依存関係を調整する必要がある。

6. 安定度・抽象度等価の原則(SAP:Stable Abstractions Principle)

 パッケージの安定度を高めるためには、抽象的でなくてはならない。一方、不安定なパッケージは具体的でなくてはならない。パッケージの抽象度を高めれば、その安定度も高くなる。その逆もまたしかり。

 抽象クラスやインターフェイスは、その派生クラスによって振る舞いが拡張される。これは抽象クラスやインターフェイスがその派生クラスより安定していることを示している。つまり、安定したパッケージを構成するためは、パッケージ内での抽象クラスやインターフェイスの割合を高めればよい。さらに、これらの抽象クラスやインターフェイスに依存する派生クラスをまとめて別のパッケージに配置すれば、そのパッケージは不安定なパッケージとなり、安定したパッケージに依存することになる。結果として自然に先の「安定依存の原則」に従うことになる。

 ここまでで設計原則について簡単に説明した。これらの設計原則やGRASPパターンに関してさらに知識を深めたいのであれば、『日経ソフトウエア』の2004年3月号から2005年2月号にかけて連載された天野 勝氏と平鍋 健児氏による「オブジェクト指向設計の考え方」が参考になるだろう。

■連載の最後に

 本連載は今回で終了である。オブジェクト指向言語であるC#やVB.NETを使っている私たち.NET開発者にとって、デザインパターンを掘り下げて学習することが、良いオブジェクト指向設計の実践へとつながっていくことを理解していただけたのではないだろうか。

 デザインパターンを実際に使ってみると、「なるほど、そういうことか。」といった類の感動を覚えることがある。その感動は、パターンによって解決される基本的な問題の理解や、抽象クラスの有効的な使い方、インターフェイスに対してプログラミングすることや、ポリモーフィズムに関することなどの「新たな発見」に対する喜びであるだろう。ぜひ、こういった感動を大事にしてもらいたい。感動は記憶を呼び起こすトリガーとなり、感動を伴った理解こそが、活きた知識の源となる。

 また、デザインパターン、GRASPパターン、設計原則を、開発者間で共通の言葉にしてしまえば、コミュニケーションの向上が期待できる。ソフトウェア開発の中心となるのは人であり、コミュニケーションは人の活動の中心となる。コミュニケーションの向上は、ソフトウェア開発を成功へと導く大きな要因となるのだ。

 最後に、これまでお付き合いいただいた読者諸氏に心から感謝の意を表したい。今後も読者の役に立つ記事を執筆していくつもりだ。次の新記事でまたお目にかかりたい。またRefactoring To Patternsに関する素晴らしい意見を提供してくれた角谷 信太郎氏にも感謝したい。End of Article


中西 庸文
Microsoft MVP for Visual Developer - Solutions Architect

1998年 太陽システム株式会社入社。Microsoft MVP for Visual Developer - Solutions Architect。オブジェクト指向、アジャイル開発プロセスの啓蒙活動に従事。現在は「協調」と「自主的な行動」を基盤としたチーム開発を行うために、プロジェクト・ファシリテーション(PF)の重要性を強く感じている。
VB&C#デザインパターンINETA Japan 加盟コミュニティ)を運営中。


 

 INDEX
  .NETで始めるデザインパターン
  第7回 デザインパターンの落とし穴
    1.デザインパターンは万能の設計ツールか?
  2.保守性を考慮したパッケージ分割の指針
 
インデックス・ページヘ  「.NETで始めるデザインパターン」


Insider.NET フォーラム 新着記事
  • 第2回 簡潔なコーディングのために (2017/7/26)
     ラムダ式で記述できるメンバの増加、throw式、out変数、タプルなど、C# 7には以前よりもコードを簡潔に記述できるような機能が導入されている
  • 第1回 Visual Studio Codeデバッグの基礎知識 (2017/7/21)
     Node.jsプログラムをデバッグしながら、Visual Studio Codeに統合されているデバッグ機能の基本の「キ」をマスターしよう
  • 第1回 明瞭なコーディングのために (2017/7/19)
     C# 7で追加された新機能の中から、「数値リテラル構文の改善」と「ローカル関数」を紹介する。これらは分かりやすいコードを記述するのに使える
  • Presentation Translator (2017/7/18)
     Presentation TranslatorはPowerPoint用のアドイン。プレゼンテーション時の字幕の付加や、多言語での質疑応答、スライドの翻訳を行える
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

注目のテーマ

Insider.NET 記事ランキング

本日 月間