Command パターン
From Wikipedia, the free encyclopedia
Command パターン(英: command pattern)は、オブジェクト指向プログラミングにおいて、処理の要求と実行を分離するためのデザインパターンである。処理内容と必要な情報を Command オブジェクトとしてカプセル化することで、処理の遅延実行やキュー管理、操作履歴の記録および取り消し(undo)などを可能にし、要求側と処理側の結合を弱めることができる。
定義
Commandパターンでは何かリクエストを実行する際、単純に処理を実行するのではなく、次のステップを踏む。
- 処理をメソッドとして内包する
Commandクラスの定義 Commandオブジェクトの生成Command.execute()メソッドのコールによるリクエスト実行[1]
すなわちリクエストを「手順書」の定義・生成とその「実行」に段階分けするパターンをとる。
Commandが内包する処理の記述は開発者に一任されている。全ての処理をCommand内に記述するパターン[2]、あるいはcommandインスタンス生成時に実行者(Receiverクラスのインスタンスreceiver、受信者もしくは観測者とも)を受け取りcommand.execute()時にreceiver.action()コールのみをおこなう、すなわち実行をReceiverに委譲してCommandは繋ぎに徹するパターンもある[3]。なお、Receiverは特にCommandパターンの要件というわけではない。

利点
このパターンがもつ利点の1つはリクエスト依頼処理と実装処理の分離(疎結合)である[6]。
例えばボタンのクリックでリクエストを実行できるUIフレームワークを開発するとする。ボタンのクリック機能とリクエストの実行はフレームワークの責務だが、リクエストの実行によって具体的に何が起こるかはアプリケーションの責務である。すなわちUIフレームワーク側はクリックに応じてリクエストを発行するが、リクエストに対してどのような処理がおこなわれるのか、そもそもリクエストの受け手が誰なのかについては関知しない[7]。ここでボタンの生成時にCommandを受け入れるとする。UIフレームワーク側はCommandがどんな処理を内包しているかは(カプセル化されているので)わからないが、execute()メソッドを実行すればリクエストが実行されることは知っている。このインターフェイス(interface)を介した契約により、クリック時にcommand.execute()するだけでクリックに応答したリクエストを実行できる。このようにCommandパターンはCommandの実行と実装を疎結合にできる。言い換えれば、CommandパターンはCommandオブジェクトのDIによる処理と実行の分離(関心の分離)である。これは手続き型プログラミングにおけるコールバック(callback)に相当する[8]。
このパターンがもつもう1つの利点はCommandが独立したオブジェクトである点である。未実行のCommandオブジェクトを配列にいれればキューイングが可能であり、それに応じた非同期処理・スケジューリングが可能になり、実行済みのCommandをキューイングすれば履歴保存とロギング・アンドゥなどが可能になる。
利用例
例としてプリンターによる印刷を考える。
手続き的に実装する場合、印刷設定を引数にとり印刷を実行するSendJobToPrinter 関数を叩けばよい。
Commandパターンで実装する場合、.Execute() 内に印刷手続きを含むPrintJob Commandを用意する。ユーザーは印刷時にPrintJob オブジェクトを作成し、印刷するドキュメント・印刷部数などのパラメータをCommandへセット、最後にプリンターへのPrintJob 送信メソッドを呼び出す。プリンター側はCommandをキューイングし準備が出来次第printJob.Execute()を実行する。
印刷にCommandパターンを利用する利点は以下である。
- コマンド情報の保持: Job名や機能を呼び出したユーザーの情報を保持、参照
- 印刷Job全体の情報提供: キューイングに利用した予測時間の提供
活用例
Command オブジェクトは下記のような機能を実現するのに便利である。
- 複数回のアンドゥ
- プログラム内の全てのユーザーの操作がコマンドオブジェクトとして実装されれば、プログラムは最近実行されたコマンドのスタックを保持することができるようになる。ユーザーがコマンドをアンドゥしたい場合、プログラムは最後に実行されたコマンドオブジェクトを単純にポップし、そのコマンドオブジェクトの
undo()メソッドを実行する。さらにアンドゥスタックを別途用意することで、アンドゥされたコマンドを再実行するリドゥ機能を実現することもできる。一般的なドキュメント編集用アプリケーションでは、ドキュメントに対する編集操作に関して、「元に戻す」「やり直し」といったメニューコマンドによってこれらの機能が提供されている。 - トランザクション的振る舞い
- アンドゥの操作が途中で失敗し、前の状態に巻き戻すこと(ロールバック)を要求されたときに不可欠である。インストーラやデータベースはこの機能を必要とする。コマンドオブジェクトは2相コミットの実現にも使用できる。
- プログレスバー
- プログラムが一連のコマンドを順番に実行する場合を考える。各コマンドオブジェクトが
getEstimatedDuration()を持っていれば、プログラムは全体の処理時間を簡単に予測することができる。全てのタスクの完了がどの程度近いのかを意味のある形で示すプログレスバーを表示できる。 - ウィザード
- ウィザードはあるアクションの設定のためにいくつかのページを表示し、最後のページで完了ボタンを押すまでアクションは実行されないことが多い。こうした場合、ユーザーインターフェイスのコードとアプリケーションのコードを分離するための自然な方法として、コマンドオブジェクトを用いたウィザードを作成することがある。コマンドオブジェクトはウィザードが最初に表示される際に作成される。ウィザードの各ページはコマンドオブジェクト内に GUI で行われた変更を保存する。完了は、
execute()を呼び出すだけである。このようにしてコマンドオブジェクトの元になるクラスには、一切ユーザーインターフェイスコードを含まないようにすることができる。 - GUI のボタンとメニューの項目
- JavaにおけるアプリケーションフレームワークのひとつSwingでは、インターフェイス
javax.swing.Actionを実装する任意のクラスのインスタンスがコマンドオブジェクトである。目的のコマンドを実行する能力に加え、Actionは関連するアイコンやキーボードショートカット、ツールチップのテキストなどを持つことができる。ツールバーのボタンやメニューのコンポーネントはActionオブジェクトのみを使って完全に初期化することができる。DelphiのVisual Component Library (VCL) では基底クラスVcl.ActnList.TActionが用意されている[9]。WPFにはSystem.Windows.Input.ICommandインターフェイスが用意されているが、WPFはさらにMVVMパターンを活用した強力なバインディングフレームワークも備えており、XAML上でGUI要素にコマンドを関連付けることもできる[10]。 - スレッドプール
- 典型的な、汎用のスレッドプールクラスは公開された
addTask()メソッドを持ち、作業項目を内部の完了待ちタスクのキューに追加する。スレッドプールクラスは、キューからコマンドを実行するスレッドのプールを管理する。キュー内の各要素はコマンドオブジェクトである。こうしたコマンドオブジェクトは典型的にjava.lang.Runnable[11]などの共通のインターフェイスを実装しており、スレッドプールクラス自体に特定のタスクがどのように用いられるかについて一切の情報がなくともコマンドを実行することができる。 - マクロの記録
- あらゆるユーザー操作がコマンドオブジェクトとして表現されていれば、コマンドオブジェクトのリストとして保持するだけで操作の流れを記録できる。その後、コマンドオブジェクトを同じ順序で実行することで、同じ操作を"再生"することができる。プログラムがスクリプトのエンジンを内蔵している場合、各コマンドオブジェクトに
toScript()メソッドを実装させ、ユーザーの操作をスクリプトとして保存することができる。 - ネットワーク
- コマンドオブジェクトそのものをネットワーク上に送出し、他のマシンに実行させることができる。たとえば、コンピュータゲームのプレイヤーの操作などである。
- 並列処理
- コマンドが共有されたリソースにタスクとして書き込まれ、多数のスレッドで並列に実行される(おそらくはリモートのマシンで)。この方式はマスター/ワーカーパターンと呼ばれていることも多い。
- 移動可能なコード
java.net.URLClassLoaderとCodebasesを経由してコードをある場所から別の場所にストリーム化・複合化できる Java などの言語を用いると、コマンドは遠隔地に搬送されるという新たな振る舞いをすることができる(EJBコマンド、マスター/ワーカー)。