GoFデザインパターン23個を完全に理解するブログ #14 Command(コマンド)

2025/11/09

プログラミング

t f B! P L
eyecatch Commandパターンは、「操作をオブジェクトとして扱う」デザインパターンです。 処理(命令)を呼び出し側と実行側で分離できるのが特徴です。 例えば「操作の履歴」「アンドゥ(取り消し)」などを簡単に実現できる。 要するに、「実行したい処理」をオブジェクトとして切り出し、「いつ・どこで・どう実行するか」を柔軟にコントロールできる仕組みです。

構造イメージ

役割 説明
Command(コマンド) 実行される処理を表すオブジェクト
Receiver(受信者) 実際に処理を行う対象(例:ライト、テレビなど)
Invoker(呼び出し元) コマンドを実行する役(ボタンなど)
Client(依頼者) コマンドをセットアップして使う人

サンプルコード

ライトのON/OFFのサンプルコードです。 // Receiver(命令を実際に実行する側) class Light { on() { console.log('ライトをつけました'); } off() { console.log('ライトを消しました'); } } // Command(操作をオブジェクト化) class LightOnCommand { constructor(light) { this.light = light; } execute() { this.light.on(); } undo() { this.light.off(); } } class LightOffCommand { constructor(light) { this.light = light; } execute() { this.light.off(); } undo() { this.light.on(); } } // Invoker(命令を実行するボタン的な存在) class RemoteControl { setCommand(command) { this.command = command; } pressButton() { this.command.execute(); } pressUndo() { this.command.undo(); } } // Client(使う側) const light = new Light(); const lightOn = new LightOnCommand(light); const lightOff = new LightOffCommand(light); const remote = new RemoteControl(); remote.setCommand(lightOn); remote.pressButton(); // ライトをつけました remote.pressUndo(); // ライトを消しました remote.setCommand(lightOff); remote.pressButton(); // ライトを消しました remote.pressUndo(); // ライトをつけました

メリット

・操作をオブジェクト化できる(履歴管理が容易) ・実行と依頼を分離できる(柔軟な構造) ・アンドゥ/リドゥ処理が自然に実装できる ・コマンドをキューやスケジューラに入れて非同期制御も可能

デメリット

・クラスやオブジェクトが増えすぎる ・設計がやや抽象的で理解しづらい ・状態管理やUndo処理の実装が煩雑になる ・小規模用途にはオーバーエンジニアリング ・デバッグがしにくい

応用例

・ゲームでの操作履歴(元に戻す機能) ・Webアプリでの「やり直し」機能 ・バッチ処理キュー ・マクロ記録(複数操作をまとめて実行)

サンプルコード2

Undo,Redoを操作するサンプルコードを作ってみました。 // Receiver(実際に操作を受ける側) class TextEditor { constructor() { this.text = ""; } addText(newText) { this.text += newText; } removeText(length) { this.text = this.text.slice(0, -length); } getText() { return this.text; } } // Command(操作をオブジェクト化) class AddTextCommand { constructor(editor, text) { this.editor = editor; this.text = text; } execute() { this.editor.addText(this.text); } undo() { this.editor.removeText(this.text.length); } } // Invoker(Commandを管理・実行する) class CommandManager { constructor() { this.history = []; this.redoStack = []; } executeCommand(command) { command.execute(); this.history.push(command); this.redoStack = []; // redoはリセット } undo() { const command = this.history.pop(); if (command) { command.undo(); this.redoStack.push(command); } } redo() { const command = this.redoStack.pop(); if (command) { command.execute(); this.history.push(command); } } } // 実行例(Client) const editor = new TextEditor(); const manager = new CommandManager(); manager.executeCommand(new AddTextCommand(editor, "Hello ")); manager.executeCommand(new AddTextCommand(editor, "World!")); console.log(editor.getText()); // "Hello World!" manager.undo(); console.log(editor.getText()); // "Hello " manager.redo(); console.log(editor.getText()); // "Hello World!"

ポイント解説

Command が「何をしたか(テキスト+長さ)」を覚えているのがポイントです。 CommandManager が「履歴」と「Redo用スタック」を持つことで、undo() / redo() を繰り返せるようになっています。 これを応用することで、いろんなシステムでのUndo,Redoを機能追加できますね。

あとがき

個人的に思ったのは、オブジェクト指向のような感じで、クラスとメソッドをセットするようなイメージですね。 事前のセットがめんどくさいですが、一度セットしてしまうと、イベント操作でも迷う事なくスラスラかけるので、事前準備型デザインパターンということがよくわかりました。 ただ、依存がディープに発生するのが容易に想像できるので、余程設計をしっかりしておかないと、機能追加時に設計破綻しかねるところは注意ポイントですね。

人気の投稿

このブログを検索

ごあいさつ

このWebサイトは、独自思考で我が道を行くユゲタの少し尖った思考のTechブログです。 毎日興味がどんどん切り替わるので、テーマはマルチになっています。 もしかしたらアイデアに困っている人の助けになるかもしれません。

ブログ アーカイブ