Menu
Coddy logo textTech

コマンドパターン

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

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

レストランに例えて考えてみましょう。シェフに何を料理するか直接伝える代わりに、注文を伝票に書き込みます。ウェイター(invoker)がその伝票(command)を厨房に運びます。厨房では、注文をキューに入れたり、優先順位を付けたり、あるいはキャンセルしたりすることも可能です。このパターンは、操作を呼び出すオブジェクトと、実際に操作を実行するオブジェクトを切り離します(デカップリング)。

このパターンには4つの主要なコンポーネントがあります:executeメソッドを宣言するCommandインターフェース、特定のアクションを実装するConcreteCommandクラス、実際の作業を実行するReceiver、そしてコマンドをトリガーするInvokerです:

interface Command {
    void execute();
}
class Light {  // レシーバー
    public void turnOn() {
        System.out.println("Light is ON");
    }
    public void turnOff() {
        System.out.println("Light is OFF");
    }
}
class LightOnCommand implements Command {
    private Light light;
    
    public LightOnCommand(Light light) {
        this.light = light;
    }
    
    public void execute() {
        light.turnOn();
    }
}
class RemoteControl {  // インボーカー
    private Command command;
    
    public void setCommand(Command command) {
        this.command = command;
    }
    
    public void pressButton() {
        command.execute();
    }
}

インボーカーはどのようなアクションが実行されるかを知りません。単に execute() を呼び出すだけです:

Light light = new Light();
Command lightOn = new LightOnCommand(light);

RemoteControl remote = new RemoteControl();
remote.setCommand(lightOn);
remote.pressButton();  // 出力: ライトはONです

コマンドパターンは、操作をキューに入れたり、元に戻す/やり直し機能を実装したり、後で実行するためにタスクをスケジュールしたりする必要がある場合に非常に優れています。各コマンドはアクションを実行するために必要なすべてをカプセル化しており、システムをより柔軟で拡張可能にします。

challenge icon

チャレンジ

簡単

Commandパターンを使って、スマートホームオートメーションシステムを構築しましょう!さまざまな家電製品をコマンドを通じて制御できるシステムを作成します。これにより、各デバイスが内部でどのように動作するかをユニバーサルリモコンが知る必要がないのと同様に、アクションをキューに入れたり、実行したり、後で使用するために保存したりすることが可能になります。

コードを4つのファイルに分けて構成します:

  • Command.java: すべてのデバイスコマンドが実装する Command インターフェースを定義します。コマンドのアクションを実行する単一のメソッド execute() を宣言する必要があります。
  • Devices.java: スマートホームデバイスを表す2つのレシーバークラスを作成します:

    Television - TV is now ON と出力する powerOn() メソッド、TV is now OFF と出力する powerOff() メソッド、および TV channel set to [channel] と出力する setChannel(int channel) メソッドを持ちます。

    Thermostat - Thermostat set to [temp] degrees と出力する setTemperature(int temp) メソッド、および Thermostat turned OFF と出力する turnOff() メソッドを持ちます。

  • Commands.java: デバイスの操作をカプセル化する具体的なコマンドクラスを作成します:

    TVOnCommand - コンストラクタで Television を受け取り、実行時に powerOn() を呼び出します。

    TVOffCommand - コンストラクタで Television を受け取り、実行時に powerOff() を呼び出します。

    TVChannelCommand - コンストラクタで Televisionint channel を受け取り、実行時にそのチャンネルで setChannel() を呼び出します。

    ThermostatSetCommand - コンストラクタで Thermostatint temperature を受け取り、実行時にその値で setTemperature() を呼び出します。

  • Main.java: コマンドを保存するための setCommand(Command cmd) メソッドと、保存されたコマンドを実行するための pressButton() メソッドを持つ RemoteControl クラス(インボーカー)を作成します。

    mainメソッドでは、TVのチャンネル番号(整数)とサーモスタットの温度(整数)の2つの入力を受け取ります。

    TelevisionThermostat を作成します。RemoteControl を作成し、以下の手順でCommandパターンのデモンストレーションを行います:

    1. TVOnCommand を設定し、ボタンを押す
    2. 入力されたチャンネルで TVChannelCommand を設定し、ボタンを押す
    3. 入力された温度で ThermostatSetCommand を設定し、ボタンを押す
    4. TVOffCommand を設定し、ボタンを押す

TVのチャンネル(整数)とサーモスタットの温度(整数)の順に2つの入力を受け取ります。

例えば、入力が 572 の場合、出力は以下のようになります:

TV is now ON
TV channel set to 5
Thermostat set to 72 degrees
TV is now OFF

RemoteControl がデバイスと直接やり取りすることはなく、Command インターフェースのみを認識していることに注目してください。各コマンドはアクションの実行に必要なすべてをカプセル化しているため、インボーカーを変更することなく、新しいデバイスやコマンドを簡単に追加できます!

チートシート

Command パターンは、リクエストをオブジェクトとしてカプセル化する振る舞いに関するデザインパターンです。これにより、クライアントを異なるリクエストでパラメータ化したり、リクエストをキューに入れたりログに記録したり、取り消し可能な操作をサポートしたりすることができます。

このパターンは、操作を呼び出すオブジェクト(インボーカー)と、実際に操作を実行するオブジェクト(レシーバー)を分離します。

主要な構成要素

  • Command: execute() メソッドを宣言するインターフェース
  • ConcreteCommand: 具体的なアクションを実装するクラス
  • Receiver: 実際の作業を実行するオブジェクト
  • Invoker: コマンドをトリガーするオブジェクト

基本構造

// Command インターフェース
interface Command {
    void execute();
}

// Receiver (レシーバー)
class Light {
    public void turnOn() {
        System.out.println("Light is ON");
    }
    public void turnOff() {
        System.out.println("Light is OFF");
    }
}

// ConcreteCommand (具体的なコマンド)
class LightOnCommand implements Command {
    private Light light;
    
    public LightOnCommand(Light light) {
        this.light = light;
    }
    
    public void execute() {
        light.turnOn();
    }
}

// Invoker (インボーカー)
class RemoteControl {
    private Command command;
    
    public void setCommand(Command command) {
        this.command = command;
    }
    
    public void pressButton() {
        command.execute();
    }
}

使用例

Light light = new Light();
Command lightOn = new LightOnCommand(light);

RemoteControl remote = new RemoteControl();
remote.setCommand(lightOn);
remote.pressButton();  // 出力: Light is ON

インボーカーはどのアクションが実行されるかを知りません。単に execute() を呼び出すだけです。これによりシステムは柔軟で拡張可能になり、操作のキューイング、元に戻す/やり直し機能の実装、またはタスクのスケジューリングに最適です。

自分で試してみよう

import java.util.Scanner;

// TODO: RemoteControl クラス(インボーカー)を以下のように作成します:
// - コマンドを保存するための setCommand(Command cmd) メソッド
// - 保存されたコマンドを実行するための pressButton() メソッド

class RemoteControl {
    // TODO: RemoteControl を実装します
}

class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        
        // 入力を読み取ります
        int channel = scanner.nextInt();
        int temperature = scanner.nextInt();
        
        // TODO: Television と Thermostat を作成します
        
        // TODO: RemoteControl を作成します
        
        // TODO: Command パターンを実演します:
        // 1. TVOnCommand をセットしてボタンを押します
        // 2. 入力された channel を使用して TVChannelCommand をセットし、ボタンを押します
        // 3. 入力された temperature を使用して ThermostatSetCommand をセットし、ボタンを押します
        // 4. TVOffCommand をセットしてボタンを押します
        
        scanner.close();
    }
}
quiz icon腕試し

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

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