Command パターン
Command パターン(英: command pattern)は、オブジェクト指向プログラミングにおいて命令あるいは動作をオブジェクトで表現するデザインパターンの一種である。
リクエストのために必要な手続きとデータをCommandオブジェクトとしてカプセル化した上で取り回し[1]、必要に応じて実行(execute)するパターンである。オブジェクトであることを生かして命令のキューイングやロギング、アンドゥ(undo)等が可能になり[2]、実際に処理を実行するサブルーチンを、オブジェクトに属するメソッドexecute()
として分離することによって、手続きと実行を疎結合にできる。
定義
[編集]Commandパターンでは何かリクエストを実行する際、単純に処理を実行するのではなく、次のステップを踏む。
- 処理をメソッドとして内包する
Command
クラスの定義 Command
オブジェクトの生成Command.execute()
メソッドのコールによるリクエスト実行[3]
すなわちリクエストを「手順書」の定義・生成とその「実行」に段階分けするパターンをとる。
Commandが内包する処理の記述は開発者に一任されている。全ての処理をCommand内に記述するパターン[4]、あるいはcommand
インスタンス生成時に実行者(Receiver
クラスのインスタンスreceiver
、受信者もしくは観測者とも)を受け取りcommand.execute()
時にreceiver.action()
コールのみをおこなう、すなわち実行をReceiver
に委譲してCommand
は繋ぎに徹するパターンもある[5]。なお、Receiverは特にCommandパターンの要件というわけではない。
Command
はAction
、Transaction
[6]あるいはTask
[7]とも呼ばれる。
利点
[編集]このパターンがもつ利点の1つはリクエスト依頼処理と実装処理の分離(疎結合)である[8]。
例えばボタンのクリックでリクエストを実行できるUIフレームワークを開発するとする。ボタンのクリック機能とリクエストの実行はフレームワークの責務だが、リクエストの実行によって具体的に何が起こるかはアプリケーションの責務である。すなわちUIフレームワーク側はクリックに応じてリクエストを発行するが、リクエストに対してどのような処理がおこなわれるのか、そもそもリクエストの受け手が誰なのかについては関知しない[9]。ここでボタンの生成時にCommand
を受け入れるとする。UIフレームワーク側はCommand
がどんな処理を内包しているかは(カプセル化されているので)わからないが、execute()
メソッドを実行すればリクエストが実行されることは知っている。このインターフェイス(interface)を介した契約により、クリック時にcommand.execute()
するだけでクリックに応答したリクエストを実行できる。このようにCommandパターンはCommandの実行と実装を疎結合にできる。言い換えれば、CommandパターンはCommand
オブジェクトのDIによる処理と実行の分離(関心の分離)である。これは手続き型プログラミングにおけるコールバック(callback)に相当する[10]。
このパターンがもつもう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
が用意されている[11]。WPFにはSystem.Windows.Input.ICommand
インターフェイスが用意されているが、WPFはさらにMVVMパターンを活用した強力なバインディングフレームワークも備えており、XAML上でGUI要素にコマンドを関連付けることもできる[12]。 - スレッドプール
- 典型的な、汎用のスレッドプールクラスは公開された
addTask()
メソッドを持ち、作業項目を内部の完了待ちタスクのキューに追加する。スレッドプールクラスは、キューからコマンドを実行するスレッドのプールを管理する。キュー内の各要素はコマンドオブジェクトである。こうしたコマンドオブジェクトは典型的にjava.lang.Runnable
[13]などの共通のインターフェイスを実装しており、スレッドプールクラス自体に特定のタスクがどのように用いられるかについて一切の情報がなくともコマンドを実行することができる。 - マクロの記録
- あらゆるユーザー操作がコマンドオブジェクトとして表現されていれば、コマンドオブジェクトのリストとして保持するだけで操作の流れを記録できる。その後、コマンドオブジェクトを同じ順序で実行することで、同じ操作を"再生"することができる。プログラムがスクリプトのエンジンを内蔵している場合、各コマンドオブジェクトに
toScript()
メソッドを実装させ、ユーザーの操作をスクリプトとして保存することができる。 - ネットワーク
- コマンドオブジェクトそのものをネットワーク上に送出し、他のマシンに実行させることができる。たとえば、コンピュータゲームのプレイヤーの操作などである。
- 並列処理
- コマンドが共有されたリソースにタスクとして書き込まれ、多数のスレッドで並列に実行される(おそらくはリモートのマシンで)。この方式はマスター/ワーカーパターンと呼ばれていることも多い。
- 移動可能なコード
java.net.URLClassLoader
とCodebasesを経由してコードをある場所から別の場所にストリーム化・複合化できる Java などの言語を用いると、コマンドは遠隔地に搬送されるという新たな振る舞いをすることができる(EJBコマンド、マスター/ワーカー)。
脚注
[編集]- ^ "This object can be stored and passed around like other objects." Gamma (1994). p.263.
- ^ "Intent. Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations." Gamma(1994). p.263.
- ^ "Execute carries out the operation." Gamma (1994). p.264.
- ^ "At the other extreme it implements everything itself without delegating to a receiver at all." Gamma (1994) p.268.
- ^ "At one extreme it merely defines a binding between a receiver and the actions that carry out the request." Gamma (1994) p.268.
- ^ "Also Known As Action, Transaction" Gamma (1994) p.263.
- ^ "Commands in THINK are called 'Tasks.'" Gamma (1994) p.272.
- ^ "Consequences ... Command decouples the object that invokes the operation from the one that knows how to perform it." Gamma(1994). p.267.
- ^ "Sometimes it's necessary to issue requests to objects without knowing anything about the operation being requested or the receiver of the request." Gamma (1994). p.263.
- ^ "Commands are an object-oriented replacement for callbacks. " Gamma (1994) p.265.
- ^ Vcl.ActnList.TAction - RAD Studio API Documentation
- ^ コマンド実行の概要 - WPF .NET Framework | Microsoft Learn
- ^ ThreadPoolExecutor (Java 2 Platform SE 5.0)
参考文献
[編集]- John Gamma, et al. (1994). Design Patterns: Elements of Reusable Object-Oriented Software. pp.263-273. Addison-Wesley Professional.
- 原典。いわゆるGoF本