今回から、新しいデザインパターンに入ろう。次にとりあげるのは、Mediatorパターンだ。いろいろなオブジェクトのやり取りを仲介して、それらの間の結びつきを弱める働きをする。Cocoaのような、ユーザインタフェースのフレームワークには欠かせないものだ。
Mediatorとは
オブジェクト指向の重要な基礎的概念の1つに、再利用化がある。一度作成したオブジェクトは、部品としてそのまま他のプロジェクトに流用出来るようにすることだ。開発効率を高める上で、非常に大切な考え方だ。
だが、実際にソフトウェアの開発を行っていると、再利用化は意外に難しいことに気づくと思う。そのような例として、GoFの本では、フォントダイアログの設計をあげている。ここでも、それに倣って説明しよう。
上にあげた図は、Mac OS Xのフォントダイアログだ。フォントのコレクション、ファミリー、書体を選択するためのカラムビュー、サイズを選択するためのテーブルビューやスライダー、選択されたフォントで描かれたサンプル文字列を表示するためのテキストフィールドがあるのが分かると思う。
いま紹介した、カラムビュー、テーブルビュー、スライダー、テキストフィールドといったものは、ユーザインタフェースの部品となるものだ。これらはクラス化を行って、ぜひ再利用出来るようにしておきたいところだ。
確かに、こういった部品を別々に設計して、クラスを作るのは容易い。だが、それらを組み合わせて、フォントダイアログのようなものを作ろうとなると、話が複雑になってくる。なぜなら、これらを強調動作させないといけないからだ。たとえば、ユーザがフォント書体を選択したら、サンプル文字列の描画を更新する。サイズの変更も、同様に再描画が必要になるだろう。もしこれらの処理を、書体を管理するカラムビューや、サイズを管理するスライダーで行うとしたら、サンプル文字列を描画しているテキストフィールドへの参照が必要になってくる。これでは、それぞれの部品の間に依存関係が出来上がってしまう。目標であった、再利用化が難しくなるだろう。
そこで登場するのが、Mediatorパターンである。Mediatorは、これらのオブジェクトのやり取りの、「仲介者」となるものだ。ここでは、FontDialogMediatorというものを考えてみよう。
まず、FontDialogMediatorは、ダイアログ上にあるすべての部品への参照を持つ。それと同時に、すべての部品からの操作を受け取るようにするのだ。そうすれば、書体の選択や、サイズの選択といった動作は、いったんFontDialogMediatorに送られるようになる。それを受けて、サンプル文字列の更新を行えばいいのだ。
これにより、それぞれの部品は、他の部品への参照を持たなくていいことになる。必要なのは、操作を送るための口と、受け取るための口だ。これは、自身で定義することができるので、他のクラスへの依存はなくなるだろう。これにより、再利用化が促進されることになる。
これが、Mediatorパターンの考え方だ。
Mediatorパターンの登場人物
こんなMediatorパターンの、登場人物を紹介しよう。登場するのは、2つのクラスだ。1つは、もちろんMediator。すべてのクラスの仲介者となるクラスだ。もう1つは、Mediatorによって仲介されるクラスになる。これを、Colleagueと呼ぼう。
MediatorとColleagueの間では、通信を行うことができる。先の例で言えば、テーブルを選択したというイベントを送ったり、テキストフィールドの中身を更新したり、というものになるだろう。Mediatorは、複数のColleagueと通信を行うことが出来る。
重要なのは、Colleague同士の間で通信を行う必要はないということだ。むしろ、直接通信を行ってはいけない、くらいまで言ってもいいかもしれない。やり取りが必要な場合は、Mediatorを通してアクセスするのが、このパターンの肝要なところだ。
MediatorとColleagueの関係は、次の図のようになるだろう。
Mediatorパターンの実装
では、Mediatorパターンの実装例を紹介しよう。ここでは、先の例に従って、フォントダイアログを作ってみる。
主要な登場人物は、3つのクラスだ。まず、フォントダイアログとなるFontDialogクラス。このダイアログの上に配置される、FontListクラスとTextFieldクラスだ。ユーザがFontListからフォントを選択すると、TextFieldのテキストが更新されるものとする。これらのやり取りを仲介するのが、FontDialogだ。
Mediatorパターンでいうと、FontDialogがMediator。FontListとTextFieldがColleagueとなる。
まずは、それぞれのクラスの宣言を、作ろう。次のようにしてみた。
List 1.
@interface FontDialog : NSObject
{
FontList* _fontList;
TextField* _textField;
}
- (void)fontSelected:(Font*)font;
@end
@interface FontList : NSObject
{
FontDialog* _fontDialog;
}
- (void)selectFont:(Font*)font;
@end
@interface TextField : NSObject
{
FontDialog* _fontDialog;
}
- (void)setFont:(Font*)font;
@end
まず、MediatorとなるFontDialogは、ColleagueであるFontListとTextFieldの参照を持っている。逆側として、FontListとTextFieldは、それぞれFontDialogの参照を持つ。注意して欲しいのは、FontListとTextFieldの間には参照がないことだ。Colleague同士の参照は、必要ない。
MediatorとColleagueの間での要求のやり取りは、メソッドで行う。FontListからFontDialogへの送信には、fontSelected:。FontDialogからTextFieldへの送信にはsetFont:を使うことにした。
実装は、こんな感じのソースコードになるだろう。
List 2.
@implementation FontDialog
- (void)fontSelected:(Font*)font
{
[_textField setFont:font];
}
@end
@implementation FontList
- (void)selectFont:(Font*)font
{
[_fontDialog fontSelected:font];
}
@end
@implementation TextField
- (void)setFont:(Font*)font
{
// テキストを更新する。
}
@end
ユーザがフォントを選択するときのシナリオに沿って説明しよう。
まず、ユーザがフォントリストからフォントを選択する。すると、FontListのselectFont:メソッドが呼びされる。この中で、FontDialogのfontSelected:メソッドを呼び出している。これは、ColleagueからMediatorへの送信になる。FontDialogのfontSelected:メソッドでは、TextFieldのsetFont:を呼び出している。これは、MediatorからColleagueの送信になる。
このように、フォントリストの選択からテキストフィールドの更新までが、Mediatorを経由して行われるのだ。これにより、部品の独立性を高めることが出来る、という訳だ。
これでMediatorパターンの説明となる訳だが、いささか簡単すぎて物足りなかったかもしれない。次回は、CocoaにおけるMediatorパターンの実装を紹介しよう。実際のアプリケーションで使われている、現実的なものになる。