GoF本 Command
ねらい
AKA
- Action
- Transaction
モチベーション
- リクエストを「とにかく出す」必要に迫られる局面がある
- 例) DOM Events / ボタン
- やりたいことは「コールバック関数」
- しかし「関数」が第一級オブジェクトでない言語では、いろいろ不便
- 拡張できない
- 状態をもたせられない
- 代わりにCommandオブジェクトを使う
つかいどころ
- コールバック関数のオブジェクト指向的な置き換え
- リクエストの指定・キューイング・実行のタイミングが異なる
- ボタンの例でいうと、リスナの登録のタイミングと、ボタンがクリックされるタイミングとは異なる
- アンドゥをサポートしたい
- コールバック関数と異なり、Commandオブジェクトは状態をもつことができる
- 「実行前」の状態を保持することで、アンドゥを実現可能
- crashから復帰したい
- Commandオブジェクトにload/store操作を実装し、ディスクに保存しておくことで実現できる
登場人物
Command
ConcreteCommand
- 生成時に受信者オブジェクトを受け取り、参照を保持する
- リクエスト受信者がどの処理を実行するかを定義する
Execute()
でこれを実行する
Client
ConcreteCommand
を生成する- この際、
Receiver
オブジェクトを指定する
- この際、
Invoker
Command::Execute()
を呼ぶ人
Receiver
- リクエストに対応して何か処理を行う人
【補】具体例
- JSの関数オブジェクトは
Command
オブジェクトということにする
const listener = function () { console.log('hoge'); }; button.addEventListener('click', listener);
Command
:Function
ConcreteCommand
オブジェクト:listener
Client
: 上記コードが書かれているモジュールInvoker
オブジェクト:button
Receiver
オブジェクト:console
button
とconsole.log('hoge')
とが疎結合になっている
クライアントコードからの利用
- クライアントコードは
ConcreteCommand
オブジェクトを生成し、Receiver
オブジェクトを指定する Invoker
オブジェクトはConcreteCommand
オブジェクトを保持するInvoker
オブジェクトはcommand.Execute()を実行することでリクエストを出す- アンドゥ可能にする場合、その前に状態を保存しておく
ConcreteCommand
オブジェクトはリクエストを受け、Receiver
オブジェクトの処理を実行する
結果
Invoker
とReceiver
との疎結合化- Commandは関数ポインタ等とは異なり第一級オブジェクトなので、如何様にも拡張できる
- Compositeにしたり
実装にあたり考えるべきこと
Command
にどれだけの知識をもたせるか- 両極端
- 知識が少ない: 処理を
receiver
に委譲するreceiver
と、receiver
に行わせたい処理だけを持つ
- 知識が多い:
ConcreteCommand
自体に処理を書くreceiver
をもたない- 適切な
receiver
がいない receiver
が分かり切っている- 新しいアプリケーションウィンドウを開く場合など
- 適切な
- 知識が少ない: 処理を
- 中くらい
receiver
を動的に探す
- 両極端
- アンドゥ・リドゥのサポート
- アンドゥ・リドゥを繰り返した際に、エラーが蓄積するのを避ける
- 最初に
Command
を実行する際の状態と、リドゥで実行する際の状態とが変わってしまうため
- 最初に
- アンドゥで巻き戻るための情報の保持
- Memento Patternをつかうとよい
- C++のtemplateをつかう
- 下記条件を満たす場合、templateで
Command<Receiver>
を作れるreceiver
の処理に引数を渡さない- アンドゥをサポートしない
- 【補】要するに、
receiver
以外の状態をもたない
- 下記条件を満たす場合、templateで
関連するパターン
- Composite
- 小さな
Command
を組み立てて大きなCommand
をつくる
- 小さな
- Memento
- アンドゥで巻き戻すための、「コマンド実行前の状態」を保持
- Prototype
- history listに登録される
Command
オブジェクトは独立したクローンである必要がある。これすなわちPrototype Patternである
- history listに登録される