Menu
Coddy logo textTech

コマンドパターン

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)システム、タスクキュー、またはマクロ記録の実装に最適です。

challenge icon

チャレンジ

簡単

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 パターンを実演します。TextDocumentTextEditor を作成します。次のコマンドを順番にキューに追加します: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: キューに入れられたすべてのコマンドを実行する
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン