コマンドパターン
CoddyのDartジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 94/110。
コマンドパターンは、リクエストをオブジェクトとしてカプセル化することで、異なるリクエストによるクライアントのパラメータ化、操作のキューイング、または取り消し(undo)機能のサポートを可能にします。メソッドを直接呼び出す代わりに、アクションをコマンドオブジェクトにラップします。このオブジェクトは、保存したり、受け渡したり、あるいは後で実行したりすることができます。
このパターンには、3つの主要なコンポーネントが含まれています。executeメソッドを持つCommandインターフェース、特定のアクションを実装するConcrete Commands(具体的なコマンド)、そして実際の作業を実行するReceiver(受信者)です。オプションのInvoker(起動者)は、コマンドが何を行うかを知ることなくコマンドをトリガーします。
abstract class Command {
void execute();
}
class Light {
void turnOn() => print('Light is ON');
void turnOff() => print('Light is OFF');
}
class TurnOnCommand implements Command {
final Light light;
TurnOnCommand(this.light);
@override
void execute() => light.turnOn();
}
class TurnOffCommand implements Command {
final Light light;
TurnOffCommand(this.light);
@override
void execute() => light.turnOff();
}
class RemoteControl {
Command? _command;
void setCommand(Command command) {
_command = command;
}
void pressButton() {
_command?.execute();
}
}
void main() {
var light = Light();
var remote = RemoteControl();
remote.setCommand(TurnOnCommand(light));
remote.pressButton(); // Light is ON
remote.setCommand(TurnOffCommand(light));
remote.pressButton(); // Light is OFF
}RemoteControlはライトについて何も知りません。単に与えられたコマンドを実行するだけです。この疎結合により、インボーカーを変更することなく新しいコマンドを簡単に追加できるようになります。Commandパターンは、アクションを第一級オブジェクトとして扱う必要がある、元に戻す/やり直し(undo/redo)システム、タスクキュー、またはマクロ記録の実装に最適です。
チャレンジ
簡単Command パターンを使用して、テキストエディタのコマンドシステムを構築しましょう!テキスト操作をコマンドオブジェクトとしてカプセル化するシステムを作成します。これにより、エディタは各操作の詳細を知ることなく、さまざまなアクションを実行できるようになります。これは Command パターンの典型的なユースケースです。各アクションがオブジェクトとして保存される、元に戻す/やり直し(undo/redo)システムのようなものだと考えてください。
コードを 2 つのファイルに整理します:
commands.dart: ここにコマンドシステムを作成します。まず、execute()メソッドを宣言する抽象クラスCommandから始めます。次に、String contentフィールド(初期状態は空)を保持し、以下の 3 つのメソッドを持つTextDocumentクラス(レシーバ)を作成します:append(String text)- content の末尾にテキストを追加しますclear()- content を空の文字列に設定しますshow()-Document: [content]を出力します
TextDocumentを操作する 3 つの具体的なコマンドを実装します:AppendCommand- コンストラクタでドキュメントとテキストを受け取り、実行時にテキストを追加しますClearCommand- コンストラクタでドキュメントを受け取り、実行時に内容をクリアしますShowCommand- コンストラクタでドキュメントを受け取り、実行時にその内容を表示します
TextEditorクラス(インボーカー)を作成します。コマンドをキューに入れるためのaddCommand(Command command)メソッドと、キューに入れられたすべてのコマンドを順番に実行するrunAll()メソッドを持つ必要があります。main.dart: コマンドファイルをインポートし、Command パターンを実演します。TextDocumentとTextEditorを作成します。次のコマンドを順番にキューに追加します:Helloを追加、Worldを追加、ドキュメントを表示、!を追加、再度表示、ドキュメントをクリア、そして最後にもう一度表示します。その後、キューに入れられたすべてのコマンドを実行します。
TextEditor が追加、クリア、表示について何も知らないことに注目してください。それは単に与えられたコマンドを実行するだけです。この疎結合こそが Command パターンの本質です!
期待される出力:
Document: Hello World
Document: Hello World!
Document: チートシート
Command パターンは、リクエストをオブジェクトとしてカプセル化し、異なるリクエストによるクライアントのパラメータ化、操作のキューイング、または取り消し(undo)機能のサポートを可能にします。
このパターンには、主に3つのコンポーネントが含まれます:
- execute メソッドを持つ Command インターフェース
- 具体的なアクションを実装する Concrete Commands(具体的なコマンド)
- 実際の作業を実行する Receiver(受信者)
- 何を行うかを知らずにコマンドをトリガーする Invoker(起動者)(オプション)
基本構造:
abstract class Command {
void execute();
}
class Receiver {
void action() => print('Action performed');
}
class ConcreteCommand implements Command {
final Receiver receiver;
ConcreteCommand(this.receiver);
@override
void execute() => receiver.action();
}
class Invoker {
Command? _command;
void setCommand(Command command) {
_command = command;
}
void trigger() {
_command?.execute();
}
}照明制御システムの例:
class Light {
void turnOn() => print('Light is ON');
void turnOff() => print('Light is OFF');
}
class TurnOnCommand implements Command {
final Light light;
TurnOnCommand(this.light);
@override
void execute() => light.turnOn();
}
class RemoteControl {
Command? _command;
void setCommand(Command command) {
_command = command;
}
void pressButton() {
_command?.execute();
}
}
void main() {
var light = Light();
var remote = RemoteControl();
remote.setCommand(TurnOnCommand(light));
remote.pressButton(); // Light is ON
}Invoker は Receiver について何も知りません。単に与えられたコマンドを実行するだけです。この疎結合により、Invoker を変更することなく新しいコマンドを簡単に追加できるようになります。
自分で試してみよう
import 'commands.dart';
void main() {
// TODO: TextDocumentのインスタンスを作成する
// TODO: TextEditorのインスタンスを作成する
// TODO: 以下のコマンドを順番にキューに追加する:
// 1. "Hello" を追加
// 2. " World" を追加
// 3. ドキュメントを表示
// 4. "!" を追加
// 5. ドキュメントを再度表示
// 6. ドキュメントをクリア
// 7. ドキュメントを最後に一度表示
// TODO: キューに入れられたすべてのコマンドを実行する
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
4Null Safety
Null Safety 入門Nullable と Non-Nullable? 演算子と ! 演算子late キーワードと Null SafetyNull-Aware 演算子クラスにおける Null Safetyまとめ:ユーザープロフィールシステム10コレクションとジェネリクス
List, Set, Map の概要型安全なコレクションジェネリッククラスジェネリックメソッドジェネリクスの制約Iterable と Iteratorまとめ:ジェネリックなストレージ