Commandパターンとは
オブジェクト指向設計において、Commandパターンは振る舞いに関するデザインパターンの一つである。メソッドの呼び出し(要求)を独立したオブジェクトとしてカプセル化することで、要求の発行者と実行者を切り離すことを目的としている。
従来のソフトウェア開発では、処理の要求元と実行元が密結合になりがちである。シンプルな呼び出しでは問題にならないが、機能の拡張や要求の記録・取消し・再実行といった操作が必要になった場合、修正が困難になる。そこで、実際の処理を直接呼び出すのではなく、命令自体をオブジェクト化して扱うことで柔軟性を高めるのがこのパターンの本事である。
現実の例として、スマートスピーカーの利用が挙げられる。「照明をつけて」と発話した際、以下の流れで処理が行われる。
- ユーザーが音声で要求を発行する
- スマートスピーカーが要求を受け取り、コマンドオブジェクトとして包裝する
- コマンドオブジェクトが、実際の実行主体(照明のコントローラ)へ指示を渡す
この過程で、ユーザーは照明のスイッチを直接操作しておらず、スマートスピーカーも照明そのものの仕組みを知る必要がない。単にコマンドを渡すだけで多様な家電を操作可能となる。
登場角色
Commandパターンは以下の要素で構成される。
- Command(抽象コマンド): 実行する要求の共通インターフェースを定義する。
- ConcreteCommand(具象コマンド): Commandを実装し、実際の実行主体(Receiver)と紐づいて具体的な処理を記述する。
- Receiver(受信者): コマンドによって実行される実際の業務ロジックを持つオブジェクト。
- Invoker(呼び出し者): コマンドの実行を要求する主体。Receiverを直接操作せず、Commandオブジェクトを通じて間接的に処理を依頼する。
- Client(クライアント): ConcreteCommandとReceiverを生成し、Invokerにコマンドを設定する。
実装例:スマートスピーカーによる照明操作
スマートスピーカーを用いて照明を操作するシナリオを実装する。まずは実行主体であるReceiverを作成する。
public class SmartLight {
public void activate() {
System.out.println("照明を点灯します...");
}
public void deactivate() {
System.out.println("照明を消灯します...");
}
}
次に、抽象コマンドインターフェースを定義する。
public interface DeviceOperation {
void perform();
}
照明を点灯・消灯する具象コマンドクラスを実装する。
public class EnableDeviceCmd implements DeviceOperation {
private SmartLight light;
public EnableDeviceCmd(SmartLight light) {
this.light = light;
}
@Override
public void perform() {
light.activate();
}
}
public class DisableDeviceCmd implements DeviceOperation {
private SmartLight light;
public DisableDeviceCmd(SmartLight light) {
this.light = light;
}
@Override
public void perform() {
light.deactivate();
}
}
呼び出し者であるスマートスピーカー(Invoker)を実装する。コマンドを外部から設定可能にし、実行を委譲する構造とする。
public class VoiceAssistant {
private DeviceOperation operation;
public void configureOperation(DeviceOperation operation) {
this.operation = operation;
}
public void executeVoiceCommand() {
System.out.print("音声アシスタント処理中 --> ");
if (operation != null) {
operation.perform();
}
}
}
最後に、クライアントで各オブジェクトを生成し、動作を確認する。
public class AppMain {
public static void main(String[] args) {
SmartLight livingRoomLight = new SmartLight();
DeviceOperation turnOn = new EnableDeviceCmd(livingRoomLight);
DeviceOperation turnOff = new DisableDeviceCmd(livingRoomLight);
VoiceAssistant assistant = new VoiceAssistant();
assistant.configureOperation(turnOn);
assistant.executeVoiceCommand();
assistant.configureOperation(turnOff);
assistant.executeVoiceCommand();
}
}
実行結果:
音声アシスタント処理中 --> 照明を点灯します...
音声アシスタント処理中 --> 照明を消灯します...
このように、クライアントはSmartLightオブジェクトを直接呼び出さず、コマンドを介して操作している。新しい家電を追加する際も、新たなコマンドクラスを作成するだけで済み、既存のコードへの影響を最小限に抑えられる。
拡張機能
- マクロコマンド: 複数のコマンドをリストで保持し、順次実行する仕組み。複数のデバイスを一括で操作する際に有効。
- 操作ログの保存: コマンドオブジェクトをシリアライズして永続化することで、システム障害時のリカバリや操作履歴の管理に活用できる。
- アンドゥ(Undo)機能: Commandインターフェースに
rollback()のような取消用メソッドを追加し、実行前の状態を保持しておくことで、データベースのトランザクションのように操作の取り消しを実現できる。
メリット・デメリット
メリット:
- 要求元と実行元の結合度を下げる
- 新規コマンドの追加が既存コードに影響を与えない
デメリット:
- コマンドの種類が増えるたびにクラス数が増大する
Commandパターンの核心は、呼び出し側と受信側の間に「コマンド」という間接層を挟むことにある。「計算機科学のいかなる問題も、別の間接層を追加することで解決できる」という有名な格言が示す通り、階層化と疎結合はソフトウェアアーキテクチャの基本理念である。