ようこそ。睡眠不足なプログラマのチラ裏です。

デザインパターン第11回「Commandパターン」

随分と間が空きましたが、気が向いたので、久しぶりに独りよがりなデザパタ講座です。
今回はGoFデザインパターンより、「Commandパターン」をぬるーく解説します。

■Commandパターンの概要■
Commandとは、「命令」を意味する言葉で、
コンピュータ用語としては、「制御信号」というような意味で用いられます。


このパターンでは、「要求(命令)」をオブジェクトとしてカプセル化することによって、
様々な要求、あるいは要求からなるキューやログなどによって、クライアントをパラメータ化します。
また、Mementoパターンと組み合わせることにより、コマンドの取り消し(Undo)や
コマンドの再実行(Redo)などのオペレーションをサポートすることもできます。

Commandパターンの特徴
Commandパターンは、要求を出すオブジェクトと処理をするオブジェクトを分離します。


このパターンは、要求を出すInvokerと要求を処理するCommandと、
要求を実現するためにオペレーションの実行方法を知っているReceiverの三つのクラスで構成されます。
オペレーション(イベント)に対する処理の流れが各ConcreteCommandクラスにカプセル化されるので、
オペレーションに対応した手順が非常に分かりやすくなるのが特徴です。
また、既存のクラスに影響を与えることなく、容易にConcreteCommandクラスを追加することもできる。


Commandパターンの協調関係

・Crientオブジェクトは、ConcreteCommandオブジェクトを生成して、それに対するReceiverを明確にする。

・Invokerオブジェクトは、ConcreteCommandオブジェクトを保持している。

・Invokerオブジェクトは、ConcreteCommandオブジェクトに対してExecuteオペレーションの呼び出しを
行うことにより要求を出す。これを取り消し可能にする場合、ConcreteCommandオブジェクトは、
実際に取り消しを行うときのために、Executeオペレーションが呼び出される前の状態をセーブしておく。


Commandパターンを利用する場面

Commandパターンは、Commandを抽象オブジェクトとして一括管理することで、
Undo処理やRedo処理、複数Commandの一括処理等を行うのに適した仕組みをフォローします。
これは、お絵かきソフト等の描画のUndoを実装するときに非常に有用である。
また、将棋やリバーシなどのテーブルゲームの「リプレイ機能」や、
「待った!機能」などの実装にも向いているので、テーブルゲームを作成するような場合に重宝するパターンと言える。
実際に作ってみた事はないが、格闘ゲームの必殺技や固有技などの
複雑なコマンド入力の評価なんかにも利用できそうである。
イマジネーション次第で、いろいろと利用価値がありそうなパターンと言える。


例えば、.NetFrameworkでLIFO*1な、簡単なUndo機能を有するCommandパターンを実装するような場合、
System.Collections.Stackクラスを使うと便利。


Commandパターンの問題点
Commandパターンの問題点として、Executeで渡される要求パラメータがある場合、
その要求パラメータを一般化しなければならないという問題点がある。


各パラメータごとの性質が異なるため、どうしても要求パラメータを一般化できないような場合、
これを解決する手段として、ConcreteCommandクラスの属性として、
ConcreteCommandごとに特有の要求パラメタを持たせる場合があるが、
当然この部分についても汎用的に記述することができない。そのため、ConcreteCommandを呼び出す側ではなく、
どうしてもConcreteCommand をインスタンス化する側で行わなければならなくなる。


すると、Commandを呼び出す時点のコンテキストに依存するパラメータを与えられなくなってしまうため、
今度はそれを避けるために、グローバルなコンテキストを表すオブジェクトを
ConcreteCommandが受け取るような形に実装されてしまうだろう。
グローバルなコンテキストというのは、結局のところグローバル変数と同義であるから、
Commandオブジェクトをまったく局所化が出来ていないことになる。


このような問題は GoF本の Commandパターンの項にも当然書いてある。
つまりパターンは、使えばいいってもんじゃない。(偉い人にはそれがわからんのですよ。)
Commandパターンが適用できるケースは、多数のオペレーション(イベント)を画一的に表すことに意義があり、
且つ、オペレーションがファーストクラスオブジェクトであるということそのものに意義がある場合、と言える。


後者について簡単に言えば、Commandをオペレーションの集約、
あるいは任意のオペレーションとして取り扱いたい場合のことです。
UndoやRedo のために操作履歴をリスト化あるいはスタック化したい場合であったり、
実行可能なオペレーションのリストが動的に変動する可能性があるような場合。
そういった要求がない場合は Commandパターンにする必然性がないと言える。


ファーストクラスオブジェクトとは?
以下、ファーストクラスが持つ権限。

・変数として名前がつけられる。
・手続きに引数として渡せる。
・手続きの結果として返される。
・データ構造に組み込める。

ファーストクラスオブジェクトとは、
簡単に言えば、「データとして扱えるオブジェクト」のことです。



面倒くさかったけど作ったオマケ
Commandパターン - VB.NET(C#) - 雑なサンプルソース

*1:Last In First Out:後入れ先出