Deep Side of Java〜Java 言語再入門 第3回 〜 クラス設計とデザインパターン

デザインパターンとは






デザインパターンとは

ちなみにデザパタ実例ページとして作った「対戦型五目並べ」が、秀和システム様より書籍として出版されることになった。タイトルは「あなたのコードを[賢く]するデザインパターン Java プログラミング」で2005年7月刊行である。詳しくはこっちを見て頂きたい。

デザインパターンとは、クラス設計の場面で頻繁に現われる「設計パターン」をまとめたものである。だから、「デザインパターン」は特定の機能や具体的なオブジェクトの詳細とは無関係に、クラス間の関係に着目して、抽象的にオブジェクト指向設計の定石を記述したものであると考えれば良い。だから、デザインパターンを知ることによって、よりよいクラス設計のアイデアを得ることができ、有効に活用すればするほど、よりよい設計になることが期待されるだけではない。現実のクラスライブラリの設計にも大きく活用されており、Java の組み込みクラスライブラリでも大いに応用されている。

オブジェクト指向設計が一般的に目指すべき目標

「オブジェクト指向設計が一般的に目指すべき目標」をとりあえず整理してみよう。デザインパターンが目的とするのは、このような目標にほかならない。

再利用性
非オブジェクト指向言語でも、ライブラリは当然再利用されており、再利用を前提とした設計がなされていることは言うまでもない。しかし、単なるアプリケーション・コードとライブラリのコードとは、現実的には価値観が異なる。ライブラリの場合には「再利用を前提とした」汎用的な設計を心がけることは当然である。オブジェクト指向言語の「再利用性」はこのようなライブラリの再利用性と異なる面がある。

異なる面としては、非オブジェクト指向言語のライブラリの場合には、「実装コードの再利用」がその主眼となる。それに対してクラスライブラリの場合には「設計の再利用」が視野に入るのである。一般に非オブジェクト指向言語ライブラリで提供されるのは「末端のライブラリ」である。つまり、アプリケーションからライブラリが呼び出されるが、その逆は稀である。sort(3) が引数として整列のための比較関数を渡されて、ライブラリコードの中からアプリケーションコードが呼び出されるような例は他には少ない。それに対して、オブジェクト指向ライブラリでは、「何かの機能を利用するための枠組」としてライブラリが提供される。つまり、具体的な実装はそのクラスライブラリを「継承して」、用意されているメソッドを使い、あるいは抽象メソッドのかたちで定義されているメソッドをそのインターフェイスを合わせて定義して利用する。

だから、このような方法論は「設計の再利用」と言うことができる。このような「設計の再利用」の中で頻繁に現われるクラス間構造を「デザインパターン」として捉えようとするのである。

ボトムアップ設計
「再利用を中心に考える」ということを逆に言えば、「オブジェクト指向設計」はボトムアップ設計の一種である。つまり、実際の機能を担当する汎用的な「オブジェクト」をまず定義し、それらのオブジェクト間の連係を考えて、一般的なアプリケーションを構築していくのである。

抽象性と具象性の分離
非オブジェクト指向言語でも、具体的なプラットフォームに依存する部分を別なファイルに分けて、別プラットフォームに移植する場合にただ1つのファイルを直せば済むようにすることが多い。プラットフォーム依存のコードは具象的な機能を実現したコードであり、それを使う呼び出し側のコードは、機能を抽象的に記述したコードであるとも考えられる。 つまり、「実装された機能」と「それを使う使い方」とを分離して設計することは、大変有効な方法論であることはいうまでもない。

オブジェクト指向言語の場合、このような関係を「抽象基底クラス」と「具象派生クラス」の関係として捉えることができる。つまり、「抽象基底クラス」の関係として、機能を抽象化して相互の関連を記述し、具体的な機能自体は抽象メソッドのままで残しておく。そして、その「抽象基底クラス」を継承して、残された抽象メソッドを「具象派生クラス」の中で、具体的な操作として定義していくことができる。これによって、「抽象基底クラス」は別の具体的なオブジェクトに対するプログラムでも再利用が可能になるのである。また、抽象記述と具象的記述とが分離されるために、ソースの可読性が高まることは言うまでもない。

操作の抽象化
非オブジェクト指向言語の場合には、操作はその具体的なデータオブジェクトに依存することが多い。たとえばある構造体に関して定義された操作を、別な構造体に適用しようとすると、大幅なコード変更が必要な場合もある。たとえば、配列で実装されているデータをリンクリストに変更する場合、どれほどの変更が必要であるか、見積もって見ればよいだろう。

オブジェクト指向言語の場合、これをうまく抽象化することができる。データはそれ自身が自分を扱う操作を「知って」いるのである。だから共通する機能に共通するインターフェイスを別々の構造体に与え、共通するインターフェイスに基づいて操作を行う限り、具体的な構造体に依存しないコードを記述できるのである。ここでオブジェクト指向言語の多相が活躍することに注意されたい。このため、デザインパターンではインターフェイスの定義(Java ならば interface, C++ ならば pure abstract class)が非常に重要な機能として活用される。

結合性の減少
このようにクラス設計をしっかりして行う限り、オブジェクト指向言語では各クラス間(モジュール間)の結合性を下げることができる。勿論、public メンバ変数を定義したり、protected メンバ変数を乱用する場合にはこの限りではない。原理的には、各クラスではメンバ変数はすべて private であり、継承関係が複雑になった場合でも、そのクラスで定義されたメンバ関数に関する操作はすべてそのクラスで完結させておくことが必要である。当然、不用意なメソッドを public にして公開すべきではなく、公開修飾子にはセンシティブになる必要がある。

可読性の向上
このように整理されたライブラリ階層構造では、結合性が減少するとともに、内部のオブジェクトに対する「責任」の所在が明確になる。これは可読性を向上させ、理解しやすいライブラリを書くことに繋がる。

クラス間の関連の整理
また、そのような抽象クラス間の関連も複雑になりがちである。これらのクラス間の関連を、デザインパターンはうまい言葉でまとめている。それゆえ、デザインパターンは設計の定石として使うことが可能である。実はその中には「Interpreter」パターンのように、一般的なプログラミングアイデアに近いものさえある。

デザインパターンの学習

だから、「デザインパターン」を学習することにによるメリットには次のようなものがあるだろう。

  1. 「結合性」が下がり、保守やデバッグ・機能改善がやりやすくなる。

  2. 「再利用性」が向上する。これはその対象とそのライブラリの「粒度」に依存するから、「別プロジェクトで使える」とまでは必ずしも言い切れないが、しかし、同一プログラムの保守や機能改善では、再利用は当然可能である。

  3. クラス設計を意識的に捉える視点が身につく。

  4. エレガントで良いプログラミング・テクニックが身につく。うまいアイデアがいろいろとあるので、皆さんはかなり勉強になるだろう。

  5. 「クラス間関連」をデザインパターンとして捉えて、それをコミュニケーションのための語彙にできる。つまり「デザインパターンで言うと**である」という言葉で何をしようとしているのかを伝えることができる。

「デザインパターン」自体は Gamma,Helm,Johnson,Vlissides の「オブジェクト指向における再利用のためのデザインパターン」(邦訳ソフトバンク刊)で初めてまとめられ、特にこの本を「GoF(Gang of Four)本」と呼ぶ。最近では解説書も何種類かある。

この「GoF」の中では23種類のデザインパターンを挙げて解説している。基本的に「GoF」の中で取り上げているパターンは、抽象的で汎用的なパターンであり、解説書の中には MVC のような具体的な対象領域を持ったパターンを追加している場合もある。

また、Java の組み込みクラスライブラリにも、応用例が沢山ある。是非、Java ライブラリのソースを読むべきである。

また、この「デザインパターン」という発想は、「GoF」で取り上げられた23種類の汎用的なパターン以外に、特定ジャンルでの「デザインパターン」も生み出している。たとえば「J2EEデザインパターン」のような、サーブレット環境に関しても「デザインパターン」が抽出されているのである。

1.3 各デザインパターンの要約

「GoF」の中で取り上げている23種のデザインパターンについて、「GoF」の「目的」の項を転載することで、簡単に要約しよう。使っている「実例」にリンクを張っているが、それらは「この講座の解説」「この講座のプログラムサンプル」およびこの講座の続編のツモリで書いている「対戦型五目並べ」のソースである。

パターン名目的実例
Abstract Factory互いに関連したり依存しあうオブジェクト群を、その具象クラスを明確にせずに生成するためのインターフェイスを提供する。3.1 Factory Method と Abstract Factory
Builder複合オブジェクトについて、その作成過程を表現形式に依存しないものにすることにより、同じ作成過程で異なる表現形式のオブジェクトを生成できるようにする。GridBagLayout サンプル Applet のソース 
通信オブジェクト・クラス
Factory Methodオブジェクトを生成するときのインターフェイスだけを規定して、実際にどのクラスをインスタンス化するかはサブクラスが決めるようにする。Factory Method パターンは、インスタンス化はサブクラスに任せる。3.1 Factory Method と Abstract Factory
Prototype生成すべきオブジェクトの種類を原型となるインスタンスを使って明確にし、それをコピーすることで新たなオブジェクトの生成を行う。 
Singletonあるクラスに対してインスタンスが1つしか存在しないことを保証し、それにアクセスするためのグローバルな方法を提供する。3.4 Singleton 
プロパティ・ライブラリ 
SQL操作ライブラリ
Adaptorあるクラスのインターフェイスを、クライアントが求める他のインターフェイスに変換する。Adaptor パターンは、インターフェイスに互換性のないクラス同士を組み合わせることができるようにする。プロパティ・ライブラリ
Bridge抽出されたクラスと実装を分離して、それらを独立に変更できるようにする。何はなくともビジネスロジック(2)
Composite部分−全体階層を表現するために、オブジェクトを木構造に組み立てる。Composite パターンにより、クライアントは、個々のオブジェクトとオブジェクトを合成したものを一様に扱うことができるようになる。 
Decoratorオブジェクトに責任を動的に追加する。Decorator パターンは、サブクラス化よりも柔軟な機能拡張方法を提供する。 
Facadeサブシステム内に存在する複数のインターフェイスに1つの統一インターフェイスを与える。Facade パターンはサブシステムの利用を容易にするための高レベルインターフェイスを定義する。ソケット操作ライブラリ
Flyweight多数の細かいオブジェクトを効率よくサポートするために共有を利用する。 
Proxyあるオブジェクトへのアクセスを制御するために、そのオブジェクトの代理、または入れ物を提供する。 
Chain of Responsibility1つ以上のオブジェクトに要求を処理する機会を与えることにより、要求を送信するオブジェクトと受信するオブジェクトの結合を避ける。受信する複数のオブジェクトをチェーン状につなぎ、あるオブジェクトがその要求を処理するまで、そのチェーンに沿って要求を渡していく。 
Command要求をオブジェクトとしてカプセル化することによって、異なる要求や、要求からなるキューやログにより、クライアントをパラメータ化する。また、取り消し可能なオペレーションをサポートする。 
Interpreter言語に対して、文法表現と、それを使用して文を解釈するインタプリタを一緒に定義する。 
Iterator集約オブジェクトが基にある内部表現を公開せずに、その要素に順にアクセスする方法を提供する。3.2 Iterater 
イテレータ
Mediatorオブジェクト群の相互作用をカプセル化するオブジェクトを定義する。Mediator パターンは、オブジェクト同士がお互いに明示的に参照し合うことがないようにして、結合度を低めることを促進する。それにより、オブジェクトの相互作用を独立に変えることができるようになる。3.7 AWTプログラムの実例 
メディエーター・クラス
Mementoカプセル化を破壊せずに、オブジェクトの内部状態を捉えて外面化しておき、オブジェクトを後にこの状態に戻すことができるようにする。 
Observerあるオブジェクトが状態を変えたときに、それに依存するすべてのオブジェクトに自動的にそのことが知らされ、また、それらが更新されるように、オブジェクト間に一対多の依存関係を設定する。3.3 Observer 
タイマ・ライブラリ
Stateオブジェクトの内部状態が変化したときに、オブジェクトが振る舞いを変えるようにする。クラス内では、振る舞いの変化を記述せず、状態を表すオブジェクトを導入することでこれを実現する。ステータス・クラス
Strategyアルゴリズムの集合を定義し、各アルゴリズムをカプセル化して、それらを交換可能にする。Strategy パターンを利用することで、アルゴリズムを、それを利用するクライアントからは独立に変更することができるようになる。SQL操作ライブラリ
Template Method1つのオペレーションにアルゴリズムのスケルトンを定義しておき、その中のいくつかのステップについては、サブクラスでの定義に任せることにする。Template Method パターンでは、アルゴリズムの定義を変えずに、アルゴリズム中のあるステップをサブクラスで再定義する。プロトコル基底クラス
Visitorあるオブジェクト構造上の要素で実行されるオペレーションを実現する。Visitor パターンにより、オペレーションを加えるオブジェクトのクラスに変更を加えずに、新しいオペレーションを定義することができるようになる。通信オブジェクト・クラス



copyright by K.Sugiura, 1996-2006