図1●データ構造を隠すIteratorパターン
図1●データ構造を隠すIteratorパターン
[画像のクリックで拡大表示]
図2●二つのクラスを結び付けるアダプタ・クラス
図2●二つのクラスを結び付けるアダプタ・クラス
[画像のクリックで拡大表示]

 プログラムの設計とは、システム全体を複数の小さなモジュールに分け、それらの関連を考えることだと言えます。その際に重要なのは、モジュール間の関連をいかにシンプルにするかです。1つのモジュールの改造が、できるだけ他のモジュールに影響を及ぼさないようにしなければなりません。これは、オブジェクト指向プログラミングに限らず、あらゆるプログラミング技法に共通したことでしょう。

 オブジェクト指向プログラミングにおけるモジュールは、クラスまたはオブジェクトです。クラス間の関連は、プログラムの静的な構造を表し、オブジェクト間の関連は、プログラムの動的な機能を表します。今回は、GoFデザインパターンの中から、オブジェクト間の関連をシンプルにするIteratorパターンと、本来つながらないクラスどうしを改造することなく関連付けてしまうAdapterパターンを紹介しましょう。どちらも5つですから、そのアイディアには、きっと感動していただけるはずです。

【お役立ち度】★★★★★
●オブジェクト間の関連をシンプルにするIteratorパターン

 今から2年ほど前、私のライター仲間であるP子さんから、GoFデザインパターンを題材とした著書をいただきました。本のオビに「こんなプログラムを疑ってみませんか」というキャッチフレーズとリスト1[拡大表示]のようなJavaのプログラムが書いてありました(若干変更してあります)。

リスト1●このプログラムのどこがいけないの?

sum = 0;
for (index = 0; index < MAXSIZE; index++) {
    sum += array[index];
}

 このプログラムは、indexという変数をループカウンタとしたfor文を使って、arrayという配列のすべての要素の合計値をsumに得るものです。配列の先頭のインデックスは0番で、末尾のインデックスは(MAXSIZE-1)番です。皆さんも、こんなfor文を書いたことがあるでしょう。何も問題ないように思えます。しかし、N氏が「疑ってみませんか」と言っているのですから、きっと何か問題があるはずです。どこがいけないのでしょうか?

 ワクワクしてきたところで、答えをお教えしましょう。このプログラムだけなら、何も問題はありません。ただし、配列の実体を持つオブジェクトと、for文で配列を使うオブジェクトが別々になっている場合は、オブジェクト間の関連が複雑になってしまうという問題があるのです。

 たとえば、オブジェクトAとオブジェクトBがあるとしましょう。オブジェクトBの中に配列の実体があり、配列を使うfor文がオブジェクトAの中にあります。オブジェクトAは、オブジェクトBの持つデータのデータ構造が配列であることを知らなければなりません。それに伴い、配列の名前や、先頭と末尾のインデックス番号といったことも知らなければなりません。もしも、インデックス番号を間違えて読み書きしたら、実行時にエラーとなってしまいます。もしも、何らかの都合で、オブジェクトBのデータ構造がスタックやリストに変更されたら、オブジェクトAにも大幅な改造が必要になってしまいます。

 この問題を解決するのがIterator(繰り返し)パターンです。これは、オブジェクトの持つデータ構造を非公開にして、hasNext()とnext()という2つのメソッドだけを公開するというものです。hasNext()は、まだ取得されていないデータが残っていればtrueを返し、残ってないならfalseを返します。next()は、順番にデータを取得して返します。オブジェクトAからオブジェクトBに、hasNext()で「データありますか?」と質問し、答えがtrueならnext()で「データください」と要求するわけです(図1[拡大表示])。

 そもそも、オブジェクトAは、オブジェクトBのデータ構造を知りたかったわけではありません。先頭から末尾までデータを取得したいだけです。オブジェクトBのデータ構造が何であっても、hasNext()とnext()だけあればデータを取得できます。オブジェクトBのデータ構造が変わったとしても、オブジェクトAのプログラムには変更が不要です。

【お役立ち度】★★★★★
●つながないクラスをつなげてしまうAdapterパターン

 複雑なつながりをシンプルにするIteratorパターンの次は、本来つながらないクラスどうしを改造することなく関連付けてしまうAdapterパターンです。どのようなアイディアなのか、給与計算システムのようなプログラム(のほんの一部)を例にして説明しましょう。

 社員を表すShainクラスと、それを使うKyuyoProgクラスがあるとします。Shainクラスのメンバは、時給の値を返すgetJikyu()メソッドと、勤務時間を返すgetJikan()メソッドです。KyuyoProgクラスのメンバは、時給×勤務時間で給与の値を返すgetKyuyo()メソッドです。getKyuyo()メソッドは、引数としてShainクラスのオブジェクトを受け取ります。リスト2[拡大表示]に、Javaのプログラムを示しますので、イメージをつかんでください。

リスト2●Shainオブジェクトを引数として受け取るメソッド

public int getKyuyo(Shain obj)
{
    return obj.getJikyu() * obj.getJikan();
}

 ここで、システムを構成する様々なクラスの中に、社長を表すShachoクラスがあることが判明しました。先ほどのgetKyuyo()メソッドで、社長の給与も計算したいと思います。ところが、Shachoクラスのメンバは、固定給の値を返すgetKoteikyu()だけになっています。このままでは、getKyuyo()メソッドの引数にShachoクラスのオブジェクト与えられません。どうしたらよいでしょう?

 この問題を解決するのが、Adapter(接続装置)パターンです。これは、見かけ上はShainクラスだが、その中身に社長を含めているという「アダプタクラス」を作るものです。Shainクラスを継承(引き継ぐこと)したアダプタクラスを作れば、見かけ上はShainクラスになります。したがって、アダプタクラスのオブジェクトをgetKyuyo()メソッドの引数に与えられます。アダプタクラスの中にShachoクラスのオブジェクトを集約(含めること)すれば、社長の固定給の情報を持つことができます。

 このアダプタクラスのメンバは、集約したShachoクラスのオブジェクトを保持するフィールドと、継承したgetJikyu()メソッドとgetJikan()メソッドになります。getJikyu()メソッドで社長の固定給を返し、getJikan()メソッドで1を返すようにオーバーライド(メソッドの処理内容の上書き変更)すれば、アダプタクラスのオブジェクトをShainクラスのオブジェクトと同様に振る舞わせることができます。

 図2[拡大表示]は、アダプタクラスの仕組みを図示したものです。白抜きの三角形が継承を表し、白抜きの菱形が集約を表しています。アダプタクラスを作るのは、面倒な作業かもしれませんが、それによって既存のShachoクラスとKyuyoProgクラスに一切変更を加えることなく、両者を結びつけることができるのです。

 アダプタクラスは、変換コネクタのようなものです。直接つながらないコネクタと差込口であっても、それらの間に変換コネクタを置くことで結び付けられます。コネクタと差込口には改造が一切不要であることがポイントです。

 次回は、Factory MethodパターンとAbstract Factoryパターンを紹介します。

矢沢久雄

グレープシティ株式会社(http://www.grapecity.com)アドバイザリースタッフ

表紙ページへ