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