コマンドパターン
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ですコマンドパターンは、操作をキューに入れたり、元に戻す/やり直し機能を実装したり、後で実行するためにタスクをスケジュールしたりする必要がある場合に非常に優れています。各コマンドはアクションを実行するために必要なすべてをカプセル化しており、システムをより柔軟で拡張可能にします。
チャレンジ
簡単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- コンストラクタでTelevisionとint channelを受け取り、実行時にそのチャンネルでsetChannel()を呼び出します。ThermostatSetCommand- コンストラクタでThermostatとint temperatureを受け取り、実行時にその値でsetTemperature()を呼び出します。Main.java: コマンドを保存するためのsetCommand(Command cmd)メソッドと、保存されたコマンドを実行するためのpressButton()メソッドを持つRemoteControlクラス(インボーカー)を作成します。mainメソッドでは、TVのチャンネル番号(整数)とサーモスタットの温度(整数)の2つの入力を受け取ります。
TelevisionとThermostatを作成します。RemoteControlを作成し、以下の手順でCommandパターンのデモンストレーションを行います:TVOnCommandを設定し、ボタンを押す- 入力されたチャンネルで
TVChannelCommandを設定し、ボタンを押す - 入力された温度で
ThermostatSetCommandを設定し、ボタンを押す TVOffCommandを設定し、ボタンを押す
TVのチャンネル(整数)とサーモスタットの温度(整数)の順に2つの入力を受け取ります。
例えば、入力が 5 と 72 の場合、出力は以下のようになります:
TV is now ON
TV channel set to 5
Thermostat set to 72 degrees
TV is now OFFRemoteControl がデバイスと直接やり取りすることはなく、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();
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
4継承
継承の基本 (extends)super キーワードメソッドのオーバーライド (@Override)コンストラクタチェーンObject クラス単一継承と多段階継承なぜ多重継承ができないのかまとめ:従業員の階層構造