コマンドパターン
CoddyのPHPジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 75/91。
コマンドパターンは、リクエストを、そのリクエストに関するすべての情報を含む独立したオブジェクトに変換する振る舞いに関するデザインパターンです。この変換により、リクエストをメソッドの引数として渡したり、リクエストの実行を遅延させたりキューに入れたり、取り消し可能な操作をサポートしたりできるようになります。
レストランを想像してみてください。
料理を注文するとき、ウェイターは注文を伝票(コマンド)に書き、それを厨房に持っていき、シェフがそれを実行します。ウェイターは料理の仕方を知る必要はありません。彼らはただコマンドを届けるだけです。
これにより、要求者と実行者が疎結合になります。
基本的な実装を以下に示します:
<?php
interface Command {
public function execute(): string;
}class Light {
public function on(): string { return "Light is ON"; }
public function off(): string { return "Light is OFF"; }
}class LightOnCommand implements Command {
public function __construct(private Light $light) {}
public function execute(): string {
return $this->light->on();
}
}class LightOffCommand implements Command {
public function __construct(private Light $light) {}
public function execute(): string {
return $this->light->off();
}
}
インボーカーは、コマンドが何をするかを知らずにコマンドを実行します:
<?php
class RemoteControl {
public function press(Command $command): string {
return $command->execute();
}
}$light = new Light();
$remote = new RemoteControl();
echo $remote->press(new LightOnCommand($light)) . "\n";
echo $remote->press(new LightOffCommand($light));
出力:
Light is ON
Light is OFFコマンドパターンは、操作をキューに入れたり、元に戻す(undo)機能を実装したり、アクションをログに記録したりする必要がある場合に優れています。各コマンドは自己完結しているため、既存のコードを修正することなく、新しいコマンドを簡単に追加できます。
チャレンジ
簡単Command パターンを使用して、テキストエディタのコマンドシステムを構築しましょう。テキストエディタでは、テキストの書き込み、削除、アクションの取り消しなどの操作をサポートする必要があることがよくあります。各操作は、統一されたインターフェースを通じて実行できる、自己完結型のコマンドオブジェクトになります。
コードを4つのファイルに分けて構成します:
Command.php—execute(): stringという単一のメソッドを持つCommandインターフェースを定義します。これにより、すべてのエディタコマンドが従うべき規約が確立されます。TextEditor.php— レシーバー(実際に作業を実行するオブジェクト)を表すTextEditorクラスを作成します。エディタは以下の機能を持つ必要があります:- 現在のテキストコンテンツを保存する(空の文字列から開始)
- コンテンツにテキストを追加し、
"Written: [text]"を返すwrite(string $text): stringメソッドを持つ - コンテンツを空にし、
"Content cleared"を返すclear(): stringメソッドを持つ - 現在のコンテンツを返す
getContent(): stringメソッドを持つ
Commands.php— Command ファイルと TextEditor ファイルをインクルードします。Commandインターフェースを実装する2つのコマンドクラスを作成します:WriteCommand— コンストラクタでTextEditorとテキスト文字列を受け取ります。そのexecute()メソッドはエディタのwrite()メソッドを呼び出し、その結果を返します。ClearCommand— コンストラクタでTextEditorを受け取ります。そのexecute()メソッドはエディタのclear()メソッドを呼び出し、その結果を返します。
main.php— Commands ファイルをインクルードします。2つの入力を受け取ります:コマンドタイプ("write"または"clear")とテキスト文字列(write コマンドの場合のみ使用)です。TextEditorインスタンスを作成します。コマンドタイプに基づいて、適切なコマンドオブジェクトを作成します。次に、コマンドのexecute()を呼び出して結果を出力するインボーカー関数またはシンプルな仕組みを作成します。新しい行に、エディタの現在のコンテンツを次の形式で出力します:
Content: [content](空の場合は、スペースの後に何も付けずにContent:とだけ出力します)。
このチャレンジでは、Command パターンが各操作をオブジェクトとしてどのようにカプセル化するかを示します。インボーカーは、write コマンドを実行しているのか clear コマンドを実行しているのかを知る必要はありません。単に execute() を呼び出し、コマンドに詳細を処理させます。
チートシート
コマンドパターンは、リクエストをそのリクエストに関するすべての情報を含む独立したオブジェクトとしてカプセル化する、振る舞いに関するデザインパターンです。これにより、リクエストをメソッドの引数として渡したり、操作をキューに入れたり、取り消し可能なアクションをサポートしたりすることができます。
このパターンは、ウェイターが料理の作り方を知らなくてもシェフに注文を届けるのと同じように、リクエストの送信者(インボーカー)と実行者(レシーバー)を分離します。
基本構造
`execute()` メソッドを持つ Command インターフェースを定義します:
interface Command {
public function execute(): string;
}実際の作業を実行するレシーバークラスを作成します:
class Light {
public function on(): string { return "Light is ON"; }
public function off(): string { return "Light is OFF"; }
}特定のアクションをカプセル化する具体的なコマンドクラスを実装します:
class LightOnCommand implements Command {
public function __construct(private Light $light) {}
public function execute(): string {
return $this->light->on();
}
}
class LightOffCommand implements Command {
public function __construct(private Light $light) {}
public function execute(): string {
return $this->light->off();
}
}インボーカー
インボーカーは、実装の詳細を知ることなくコマンドを実行します:
class RemoteControl {
public function press(Command $command): string {
return $command->execute();
}
}
$light = new Light();
$remote = new RemoteControl();
echo $remote->press(new LightOnCommand($light)) . "\n";
echo $remote->press(new LightOffCommand($light));メリット
コマンドパターンは、操作のキューイング、取り消し(Undo)機能の実装、またはアクションのログ記録に最適です。各コマンドは自己完結しているため、既存のコードを変更することなく新しいコマンドを簡単に追加できます。
自分で試してみよう
<?php
require_once 'Commands.php';
// 入力を読み込む
$commandType = trim(fgets(STDIN)); // "write" または "clear"
$text = trim(fgets(STDIN)); // writeコマンド用のテキスト
// TODO: TextEditorのインスタンスを作成する
// TODO: コマンドタイプに基づいて、適切なコマンドオブジェクトを作成する
// - "write" の場合は、エディタとテキストを使用してWriteCommandを作成する
// - "clear" の場合は、エディタを使用してClearCommandを作成する
// TODO: コマンドのexecute()を呼び出し、結果を出力する
// インボーカー(実行者)の仕組みを作成する
// TODO: エディタの現在の内容を "Content: [content]" の形式で出力する
// (空の場合は、スペースの後に何も付けずに "Content: " とだけ出力する)
?>このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。