Menu
Coddy logo textTech

コマンドパターン

CoddyのC#ジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 56/70。

Commandパターンは、リクエストを、そのリクエストに関するすべての情報を含む独立したオブジェクトに変換する振る舞いに関するデザインパターンです。この変換により、リクエストをメソッドの引数として渡したり、キューに入れたり、ログに記録したり、あるいは取り消し可能な操作をサポートしたりすることができます。

このパターンは、操作を呼び出すオブジェクトと、その操作の実行方法を知っているオブジェクトを分離します。これは、コマンドインターフェース、具体的なコマンド、レシーバー(実際の作業を行うオブジェクト)、およびインボーカー(コマンドをトリガーするオブジェクト)の4つの主要なコンポーネントで構成されています。

public interface ICommand
{
    void Execute();
}

public class Light  // レシーバー
{
    public void TurnOn() => Console.WriteLine("Light is ON");
    public void TurnOff() => Console.WriteLine("Light is OFF");
}

public class TurnOnCommand : ICommand
{
    private Light _light;
    
    public TurnOnCommand(Light light) => _light = light;
    
    public void Execute() => _light.TurnOn();
}

public class TurnOffCommand : ICommand
{
    private Light _light;
    
    public TurnOffCommand(Light light) => _light = light;
    
    public void Execute() => _light.TurnOff();
}

インボーカーは、コマンドの内容を知ることなく、コマンドを保持し実行します:

public class RemoteControl  // インボーカー
{
    private ICommand _command;
    
    public void SetCommand(ICommand command) => _command = command;
    
    public void PressButton() => _command.Execute();
}

// 使用例
var light = new Light();
var remote = new RemoteControl();

remote.SetCommand(new TurnOnCommand(light));
remote.PressButton();  // ライトが点灯

remote.SetCommand(new TurnOffCommand(light));
remote.PressButton();  // ライトが消灯

コマンドパターンは、取り消し(Undo)機能、マクロ記録、または後で実行するための操作のキューイングを実装するのに特に強力です。各コマンドはアクションを実行するために必要なすべてをカプセル化しており、システムをより柔軟で拡張可能にします。

challenge icon

チャレンジ

簡単

Command パターンを使用して、テキストエディタのコマンドシステムを構築しましょう。テキストの挿入や削除といった編集操作をコマンドオブジェクトとしてカプセル化し、エディタが統一されたインターフェースを通じてそれらを実行できるシステムを作成します。

コードは以下の3つのファイルに分けて構成します:

  • Command.cs: TextEditor 名前空間内に、単一のメソッド Execute() を持つ ICommand インターフェースを定義します。次に、2つの具体的なコマンドクラスを作成します:
    • InsertCommand - コンストラクタで Document と文字列 text を受け取ります。実行されると、ドキュメントにテキストを追加し、Inserted: {text} と出力します。
    • DeleteCommand - コンストラクタで Documentint count を受け取ります。実行されると、ドキュメントから末尾の count 文字を削除し、Deleted: {count} characters と出力します。
  • Document.cs: 同じ名前空間に Document クラスを作成します。これはレシーバー(実際にテキスト操作を行うオブジェクト)です。以下の要素を持つ必要があります:
    • コンテンツを格納するためのプライベートな文字列フィールド(空文字列で初期化)
    • コンテンツにテキストを追加する Append(string text) メソッド
    • 末尾の count 文字を削除する RemoveLast(int count) メソッド(count がコンテンツの長さを超える場合は、すべてのコンテンツをクリアします)
    • 現在のコンテンツを返す GetContent() メソッド
  • Program.cs: インボーカー(呼び出し元)として機能する Editor クラスを作成します。これには SetCommand(ICommand command) メソッドと、現在のコマンドを実行する ExecuteCommand() メソッドが必要です。メインコードでは、一連の編集操作を処理することで Command パターンを実演してください。

以下の入力を受け取ります:

  • 実行するコマンドの数
  • 各コマンドについて:コマンドタイプ(insert または delete)、続いて挿入するテキストまたは削除する文字数

すべてのコマンドを実行した後、最終的なドキュメントの内容を新しい行に Content: {content} の形式で出力してください。

例えば、入力が以下の場合:

4
insert
Hello
insert
 World
delete
3
insert
!

出力は以下のようになります:

Inserted: Hello
Inserted:  World
Deleted: 3 characters
Inserted: !
Content: Hello Wo!

Editor(インボーカー)がテキスト操作について何も知らないことに注目してください。それは単に与えられたコマンドを実行するだけです。コマンドがアクションとレシーバーの両方をカプセル化しているため、エディタを変更することなく ReplaceCommand のような新しい操作を簡単に追加できます。

チートシート

コマンドパターンは、リクエストを、そのリクエストに関するすべての情報を含む独立したオブジェクトとしてカプセル化する振る舞いに関するパターンです。これにより、リクエストをメソッドの引数として渡したり、キューに入れたり、ログに記録したり、取り消し可能な操作をサポートしたりすることができます。

このパターンには4つの主要なコンポーネントがあります:

  • Commandインターフェース - Execute()メソッドを定義します
  • 具体的なコマンド (Concrete commands) - コマンドインターフェースを実装し、特定のアクションをカプセル化します
  • レシーバー (Receiver) - 実際の作業を実行するオブジェクト
  • インボーカー (Invoker) - 実装を知ることなくコマンドをトリガーするオブジェクト

Commandインターフェースと具体的なコマンド

public interface ICommand
{
    void Execute();
}

public class TurnOnCommand : ICommand
{
    private Light _light;
    
    public TurnOnCommand(Light light) => _light = light;
    
    public void Execute() => _light.TurnOn();
}

public class TurnOffCommand : ICommand
{
    private Light _light;
    
    public TurnOffCommand(Light light) => _light = light;
    
    public void Execute() => _light.TurnOff();
}

レシーバー

public class Light
{
    public void TurnOn() => Console.WriteLine("Light is ON");
    public void TurnOff() => Console.WriteLine("Light is OFF");
}

インボーカー

public class RemoteControl
{
    private ICommand _command;
    
    public void SetCommand(ICommand command) => _command = command;
    
    public void PressButton() => _command.Execute();
}

使い方

var light = new Light();
var remote = new RemoteControl();

remote.SetCommand(new TurnOnCommand(light));
remote.PressButton();  // ライトはONです

remote.SetCommand(new TurnOffCommand(light));
remote.PressButton();  // ライトはOFFです

コマンドパターンは、取り消し機能の実装、マクロ記録、または後で実行するための操作のキューイングに役立ちます。各コマンドはアクションの実行に必要なすべてをカプセル化するため、システムがより柔軟で拡張可能になります。

自分で試してみよう

using System;
using TextEditor;

// TODO: Editorクラス(インボーカー)を作成する
public class Editor
{
    // TODO: 現在のコマンドを保持するためのプライベートフィールドを追加する
    private ICommand _command;

    // TODO: SetCommandメソッドを実装する
    public void SetCommand(ICommand command)
    {
        // TODO: コマンドを保存する
    }

    // TODO: ExecuteCommandメソッドを実装する
    public void ExecuteCommand()
    {
        // TODO: 現在のコマンドを実行する
    }
}

class Program
{
    public static void Main(string[] args)
    {
        // コマンドの数を読み込む
        int n = Convert.ToInt32(Console.ReadLine());

        // ドキュメント(レシーバー)を作成する
        Document document = new Document();

        // エディタ(インボーカー)を作成する
        Editor editor = new Editor();

        // TODO: 各コマンドを処理する
        for (int i = 0; i < n; i++)
        {
            string commandType = Console.ReadLine();

            // TODO: commandTypeに基づいて、適切なコマンドを作成する
            // - "insert"の場合:テキストを読み込み、InsertCommandを作成する
            // - "delete"の場合:文字数を読み込み、DeleteCommandを作成する
            // その後、エディタにコマンドをセットして実行する
        }

        // TODO: 最終的な内容を "Content: {content}" の形式で出力する
    }
}
quiz icon腕試し

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

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