DPDesign Pattern Quest
#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 問のクイズで、 このパターンを身体に染み込ませよう。

挑戦する →