#14 / 23
B振る舞いDIFF
コマンド
Command
操作を『注文票』にして渡す。ファミレスのオーダーを思い出せ ── あれが Undo もキューも履歴もこなす。
Intent · 目的
「やってほしいこと」をオブジェクトに包んで、依頼者と実行者を切り離す。注文票をテーブルに置けば、誰が運んでもいいし、後でキャンセルできるし、履歴にも残せる。
Motivation · 動機
GUI で「このボタンを押したら○○」を直接書いてしまうと、ショートカットキーやメニュー、コマンドパレット ── 同じ処理を別の入口から呼びたくなった瞬間にコピペが始まる。さらに『Undo を実装してください』と言われた日には、各処理に逆操作を継ぎ足し回ることになる。Command は『何をするか』を Object として独立させる。実行も逆操作も履歴保存も、全部その Object が知っているから、ボタンもショートカットも『このコマンドを叩く』と書くだけで仕事完了。
適用場面
- 1ボタン・ショートカット・メニュー ── 同じ処理を複数の入口から呼びたい UI。
- 2Undo / Redo / マクロ / リプレイを実装したい。
- 3操作をキューに詰めて非同期実行したい、リトライしたい、ログに残したい。
概念図
サンプルコード
interface Command { execute(): void; undo(): void; }
class Light {
on = false;
turnOn(){ this.on = true; console.log("light on"); }
turnOff(){ this.on = false; console.log("light off"); }
}
class TurnOnCommand implements Command {
constructor(private light: Light) {}
execute(){ this.light.turnOn(); }
undo(){ this.light.turnOff(); }
}
class Remote {
private history: Command[] = [];
press(c: Command){ c.execute(); this.history.push(c); }
undo(){ this.history.pop()?.undo(); }
}
const light = new Light(); const r = new Remote();
r.press(new TurnOnCommand(light));
r.undo();Pros · メリット
- +依頼者と実行者が疎結合。ボタンも音声操作も、同じコマンドを叩くだけ。
- +Undo・履歴・キュー・リプレイ・マクロが、ほぼ無料で手に入る。
- +非同期実行・遅延実行・リトライの仕組みに自然に乗る。
Cons · デメリット
- −些細なメソッド呼び出しまでコマンド化すると、クラス爆発と過剰設計の二重苦。
- −本気で Undo 機能を実装するなら『逆操作』の設計が必要で、思ったより難しい。
- −コマンドが増えると『どれを呼ぶべきか』の選定責務が誰かに乗ってくる。
関連パターン
⚔ ready to test
理解度を確かめる時間だ
3 問のクイズで、 このパターンを身体に染み込ませよう。