ねらい
-
リクエストをオブジェクトとしてカプセル化
- クライアントを異なるリクエストでパラメタライズできるようにする
- リクエストのキューイング・ロギングを可能にする
- アンドゥのサポートを可能にする
AKA
- Action
- Transaction
モチベーション
-
リクエストを「とにかく出す」必要に迫られる局面がある
- リクエストを誰が受信するのかを知らない
- リクエストを受信したモジュールがどんなことをするかを知らない
-
例) DOM Events / ボタン
- アプリケーションは、ボタンのclickイベントのリスナ関数を登録する
- ボタンはクリックされたら、単にリスナ関数を実行するだけ = リクエストを「とにかく出す」
-
リスナ関数の中に知識がカプセル化されている
- どのモジュールの
- どの操作を呼び出す
- やりたいことは「コールバック関数」
-
しかし「関数」が第一級オブジェクトでない言語では、いろいろ不便
- 拡張できない
- 状態をもたせられない
- 代わりにCommandオブジェクトを使う
つかいどころ
- コールバック関数のオブジェクト指向的な置き換え
-
リクエストの指定・キューイング・実行のタイミングが異なる
- ボタンの例でいうと、リスナの登録のタイミングと、ボタンがクリックされるタイミングとは異なる
-
アンドゥをサポートしたい
- コールバック関数と異なり、Commandオブジェクトは状態をもつことができる
- 「実行前」の状態を保持することで、アンドゥを実現可能
-
crashから復帰したい
- Commandオブジェクトにload/store操作を実装し、ディスクに保存しておくことで実現できる
登場人物
-
Command
-
実行可能インタフェースを定義する
Execute()
とか- C++なら()演算子のオーバーロードとか
-
-
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
を動的に探す
-
-
アンドゥ・リドゥのサポート
Unexecute()
といったインタフェースを定義する-
1-level アンドゥ: 最後に実行したコマンドを保持すればよい
lastCommand->Unexecute()
で戻れる
-
任意回数戻れるアンドゥ: history listが必要
- これまで実行してきた
Command
オブジェクトのリスト Command
はアンドゥで戻るために状態を保持する必要がある- ので、history list上の
Command
オブジェクトは、一つ一つが独立した状態を持つ必要がある -
つまり、history listにはコマンドのクローンを登録する必要がある
- Prototype Pattern
- これまで実行してきた
-
アンドゥ・リドゥを繰り返した際に、エラーが蓄積するのを避ける
- 最初に
Command
を実行する際の状態と、リドゥで実行する際の状態とが変わってしまうため
- 最初に
-
アンドゥで巻き戻るための情報の保持
- Memento Patternをつかうとよい
-
C++のtemplateをつかう
-
下記条件を満たす場合、templateで
Command<Receiver>
を作れるreceiver
の処理に引数を渡さない- アンドゥをサポートしない
- 【補】要するに、
receiver
以外の状態をもたない
-
関連するパターン
-
Composite
- 小さな
Command
を組み立てて大きなCommand
をつくる
- 小さな
-
Memento
- アンドゥで巻き戻すための、「コマンド実行前の状態」を保持
-
Prototype
- history listに登録される
Command
オブジェクトは独立したクローンである必要がある。これすなわちPrototype Patternである
- history listに登録される