CompositeパターンとCommandパターンの事例

2009年5月22日(金)
中川 三千雄

汎用的なデザインパターンフレームワークを実現する

 前回(第3回:http://thinkit.jp/article/933/1/)はAdapterパターンとFactory Methodパターンの事例を紹介しました。

 Adapterパターンは、既存クラスのインターフェースを変えることなく、利用者が望む別のインターフェースに変換して利用するパターンです。既存クラスをそのまま利用した場合、別のクラスに変更しようとすると大幅な修正が必要になりますが、Adapterパターンでラップして利用することで最小限の修正で済ますことができます。

 一方、Factory Methodパターンはインスタンスを生成する仕組みとインスタンスを決定する仕組みを分けるパターンです。生成するインスタンスを後から決めたり、変更したりする場合に有効です。

 今回はChain of Responsibilityパターンの汎用的なフレームワークであるcommons chain 1.2(http://commons.apache.org/chain/)を事例に、CompositeパターンとCommandパターン、そしてChain of Responsibilityパターンを取り上げます。これらはGoFのデザインパターンです。

 Compositeパターン(図1-1)は構造に分類されるパターンです。Compositeとは「複合物」や「混合物」という意味です。Composite役は「器」であり、Leaf役はその器の「中身」です。そして「器」には別の「器」も入れることができます。Compositeパターンはツリー構造のような再帰的な構造を実現できます。また、このパターンで実現した構造内にあるすべてのオブジェクトに対して、Client役は違いを意識することなくComponent役で定義した操作を行うことができます。

 ファイルシステムを例にすると、Composite役はフォルダになり、Leaf役はファイルになります。フォルダにはファイルや別のフォルダを入れることができます。またComponent役の持つメソッドは、フォルダとファイルに共通する「コピー」「貼り付け」「削除」などの操作になります。ファイルシステムは、フォルダやファイルの違いを意識することなく、それらの操作が行えます。

 Commandパターン(図1-2)はふるまいに分類されるパターンです。Commandとは「命令」という意味です。

 通常、オブジェクトに対して何かの操作をするときには、メソッドを呼び出します。呼び出した結果はオブジェクトに反映されますが、メソッドを呼び出した履歴は残りません。このパターンはReceiver役の「メソッドの呼び出し」を命令クラス(ConcreteCommand役)としてカプセル化します。命令クラスは共通API(Command役)のexecuteメソッドを実装します。そのメソッドではReceiver役の「メソッドの呼び出し」を行います。「メソッドの呼び出し」を命令クラスのインスタンスで表すことで実行した命令の履歴を残すことができます。

 また、例えば写真の撮影において「3秒待つ」命令や「カメラのシャッターを押す」命令がある場合、それらの命令のインスタンスをあらかじめ生成しておき、まとめて実行することで「オート撮影」命令を疑似的に作り出すことができます。Commandパターンはこのように命令のインスタンスを組み合わせて新しい命令を作り出すこともできるのです。

commons chainで実現するChain of Responsibilityパターンとは?

 Chain of Responsibilityパターン(図1-3)はふるまいに分類されるパターンです。

 Chain of Responsibilityは「責務の連鎖」という意味になります。責務とは「自分の責任として果たさねばならない事柄」のことであり、このパターンではConcreteHandler役がその責務を持っています。

 ConcreteHandler役の責務はClient役から要求があった場合、自分が果たす「要求に対する処理」を行うことです。一方、Handler役はConcreteHandler役に要求を出すための共通のAPIを定めます。またHandler役は自身を鎖状につなぐ「チェーン構造」になっています。

 このパターンは、それぞれの「要求に対する処理」を持つConcreteHandler役を鎖状につなぎ、「チェーン構造」にすることで1つの処理を形成します。Client役はその「チェーン構造」の処理に要求を出すことで、最終的に要求を満たします。

 例えば図1-4のような献血のサービスがあるとします。そのサービスでは血液型ごとに採血する担当者(ConcreteHandler役)が割り当てられています。O型の人(Client役)が採血を依頼した場合、O型の人を採血する担当者になるまで次々と採血担当者を渡り歩き、最終的に献血という要求を満たします。

 今回はcommons chainの中で、CompositeパターンとCommandパターンがどのように適用されているのか、どのような効果があるのかを分析して理解を深めていきましょう。

着目するポイント

 一般的なChain of ResponsibilityパターンはConcreteHandler役が「チェーン構造」と「要求に対する処理」の両方の責務を持つように設計します。しかし、「チェーン構造」は特殊なケースを除いては同じ操作と構造になるはずです。なぜなら「チェーン構造」の役割はConcreteHandler役をチェーンにつないだり、変更したり、削除したりできればよいからです。

 一方、「要求に対する処理」は要求ごとに処理を用意する必要があります。よって、それらの処理は重複することがなく、すべて異なります。

 汎用的なフレームワークを実現する以上、「チェーン構造」に関する機能はフレームワーク側で提供し、フレームワークの利用者は「要求に対する処理」に集中してプログラミングできるようにするべきです。

 今回は「チェーン構造」と「要求に対する処理」を切り離すところに着目して、汎用的なフレームワークを作成します。

チェーン構造をツリー構造に置き換える

 まずは図2-1をご覧ください。Chain of Responsibilityパターンのチェーン構造はConcreteHandler役のスーパークラスであるHandler役が持っています。Handler役は自分自身の関連を持つことでチェーン構造を実現しています。そのためサブクラスであるConcreteHandler役にもその特性が継承されます。

 ConcreteHandler役からチェーン構造を取り除くため、Handler役のチェーン構造の部分にCompositeパターンを適用します。Compositeパターン適用後のクラス図を見るとComponent役とComposite役の関係で再帰的な構造を実現していることがわかります。その再帰的な構造は、図2-2のようなツリー構造に変化します。その図を見てわかるようにComposite役がツリー構造を管理する役割を持ち、Leaf役が要求に対する処理の役割を持つように分かれます。

 Chain of Responsibilityパターンのチェーン構造を、Compositeパターンを適用したツリー構造に置き換えることで、ConcreteHandler役が抱えていた「チェーン構造」と「要求に対する処理」の両方の責務を持つ問題を解決できます。

要求に対する処理をコマンド化する

 Compositeパターンを適用したことでツリー構造を管理するComposite役と要求に対する処理の役割を持つLeaf役に分けて考えることができるようになりました。しかし、これで汎用的なフレームワークが完成したとはいえません。なぜならComponent役に、Composite役にのみ必要なメソッドが含まれているからです。このままではLeaf役にComposite役に関係のないメソッドを実装する可能性が残ります。

 Component役のメソッドを整理すると要求を出すrequestメソッド以外はすべてComposite役のメソッドです。そこで図3-1のように要求を出すrequestメソッドとそれ以外のメソッドを分けてインターフェースを再定義します。さらに「要求を出す」部分にCommandパターンを適用します。

 ちなみに、Chain of Responsibilityパターンも「要求に対する処理」をインスタンス化してチェーン構造にします。処理の呼び出しをインスタンス化するという、その考え方はCommandパターンに通じるものがあります。

 Chain of ResponsibilityパターンにCompositeパターンとCommandパターンを適用したことで「ツリー構造」と「要求に対する処理」をうまく切り離すことができました。

 フレームワークの利用者は【利用例4-A】のように「要求に対する処理」の実現に集中してプログラミングができるようになります。また【利用例4-A】の(1)(2)のように複数の要求に対する処理(コマンド)を組み合わせて新しいコマンドを作ることができます。

【利用例4-A】
public class TelSupportContext extends Context {
  // 要求の内容を表すパラメータを構成する
}
 :

public class InitStandardSupport implements Command {
  public boolean request(Context context) { … } // 一般サポート初回
}
 :

public class StandardSupport implements Command {
  public boolean request(Context context) { … } // 一般サポート継続
}
 :
public class InitSpecialSupport implements Command {
  public boolean request(Context context) { … } // 特別サポート初回
}
 :

public class SpecialSupport implements Command {
  public boolean request(Context context) { … } // 特別サポート継続
}
 :

// 一般サポート ……(1)
CompositeCommand standardSupport = new ConcreateCompositeCommand();
standardSupport.add(new InitStandardSupport());
standardSupport.add(new StandardSupport());

// 特別サポート ……(2)
CompositeCommand specialSupport = new ConcreateCompositeCommand();
specialSupport.add(new InitSpecialSupport());
specialSupport.add(new SpecialSupport());

// 電話サポート
CompositeCommand telSupport = new ConcreateCompositeCommand();
telSupport.add(standardSupport);
telSupport.add(specialSupport);

// 要求を投げる
Context ctx = new TelSupportContext(); // 問い合わせ内容
telSupport.request(ctx);
 :

■commons chainに関する補足

 commons chainは処理のチェーンをCompositeパターンで形成し、要求に対する処理の実行をCommandパターンで実現しています(図3-2)。

 Chainインターフェースを実装したChainBaseクラスはCompositeパターンのComposite役であり、ツリー構造を実現しています。

CommandインターフェースはCommandパターンのCommand役であり、要求を出すための共通APIの役割を持ちます。ChainBaseクラスはその共通APIから要求を受けると自身で管理しているCommandインターフェースを持つオブジェクト(要求に対する処理)をすべて実行します。一方、要求に対する処理は、Commandインターフェース、またはFilterインターフェースを直接実装することで実現しています。

 Chain of Responsibilityパターンでは要求に対する処理を呼び出す場合、その呼び出しを再帰呼び出しで実現することがあります。再帰呼び出しはメソッドを呼び続けて要求を満たしたとき、呼び出し元に戻っていく性質があります。その戻っていくタイミングで「戻りの処理」を行うことができます。commons chainではCommandインターフェースのかわりにFilterインターフェースを実装することで、再帰呼び出しと同様の「戻りの処理」も行うことができます(図3-3)。

CompositeパターンとCommandパターンは最強コンビ!?

 今回紹介したcommons chainは個人的にCommandパターンのフレームワークのように感じましたが、Filterインターフェースを実装することで再帰呼び出しの「戻りの処理」を実現できるところは作者たちのChain of Responsibilityパターンへのこだわりを感じます。ちなみにcommons chain はstruts 1.3系の標準リクエストプロセッサ「ComposableRequestProcessor」でHTTPリクエストを処理するために利用されています。

 このほかにも、CompositeパターンとCommandパターンの組み合わせは、複数のデータベースの更新を1つのトランザクションとして実行する場合など、いくつかの処理をまとめて実行するような仕組みを作る場合によく利用します。

 次回はいよいよ最終回です。最終回にふさわしい事例を紹介します。それではお楽しみに!


【参考文献】

Erich Gamma, Rechard Helm, Ralph Jonson, John Vlissides『オブジェクト指向における再利用のためのデザインパターン』ソフトバンククリエイティブ(発行年:1999)
結城 浩『Java言語で学ぶデザインパターン入門』ソフトバンクパブリッシング(発行年:2001)
「Apache Commons - commons chain」(http://commons.apache.org/chain/) (アクセス:2009/04)

株式会社オージス総研
株式会社オージス総研アドバンストモデリングソリューション部アーキテクトチーム兼エンタープライズオープンソースセンター所属。これまでに2社の独立系SI企業で経験を積み、現在に至る。オージス総研では、フレームワークの設計/開発や開発プロセス支援など、アーキテクトとしてプロジェクトに参画する。また、オージス総研のコミュニティー「オブジェクトの広場」にも参加している。
オブジェクトの広場 http://www.ogis-ri.co.jp/otc/hiroba/

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています