コマンドデザインパターン
コマンドパターンは、ビヘイビアルデザインパターンの一つです。コマンドデザインパターンは、要求と応答のモデルで緩い結合を実現するために使用されます。
コマンドパターン
コマンドパターンでは、リクエストはインボーカーに送信され、インボーカーがカプセル化されたコマンドオブジェクトにそれを渡します。コマンドオブジェクトは、特定のアクションを実行するために、リクエストをレシーバーの適切なメソッドに渡します。クライアントプログラムはレシーバーオブジェクトを作成し、それをコマンドにアタッチします。そして、インボーカーオブジェクトを作成し、アクションを実行するためのコマンドオブジェクトをアタッチします。クライアントプログラムがアクションを実行する際には、コマンドとレシーバーオブジェクトに基づいて処理されます。
コマンドデザインパターンの例をひとつ示してください。
私たちは、Commandパターンを実装できる実際のシナリオを見てみましょう。ファイルシステムユーティリティを提供し、ファイルの開閉と書き込みのメソッドを持つことを考えます。このファイルシステムユーティリティは、WindowsやUnixなどの複数のオペレーティングシステムをサポートする必要があります。File Systemユーティリティを実装するためには、まず実際の作業を行う受信者クラスを作成する必要があります。Javaではインターフェースを使ってコードを記述するので、Windows、Unix、Solarisなどの異なるオペレーティングシステムの味付けのためにFileSystemReceiverインターフェースとその実装クラスを持つことができます。
コマンドパターンのレシーバークラス
package com.scdev.design.command;
public interface FileSystemReceiver {
void openFile();
void writeFile();
void closeFile();
}
FileSystemReceiver(ファイルシステムレシーバー)インターフェースは、実装クラスのための契約を定義します。簡単のために、UnixおよびWindowsシステムと互換性のある2つの受信者クラスのバリエーションを作成しています。
package com.scdev.design.command;
public class UnixFileSystemReceiver implements FileSystemReceiver {
@Override
public void openFile() {
System.out.println("Opening file in unix OS");
}
@Override
public void writeFile() {
System.out.println("Writing file in unix OS");
}
@Override
public void closeFile() {
System.out.println("Closing file in unix OS");
}
}
package com.scdev.design.command;
public class WindowsFileSystemReceiver implements FileSystemReceiver {
@Override
public void openFile() {
System.out.println("Opening file in Windows OS");
}
@Override
public void writeFile() {
System.out.println("Writing file in Windows OS");
}
@Override
public void closeFile() {
System.out.println("Closing file in Windows OS");
}
}
「Override」アノテーションに気づきましたか?それがなぜ使用されるのか気になる場合は、Javaのアノテーションと「Override」アノテーションの利点をお読みください。今、レシーバークラスが準備できたので、コマンドクラスの実装に移ることができます。
コマンドパターンのインタフェースと実装
私たちは、基本的なコマンドを作成するためにインターフェースまたは抽象クラスを使用することができます。これは設計上の決定であり、要件に応じて異なります。デフォルトの実装がないため、私たちはインターフェースを選択しています。
package com.scdev.design.command;
public interface Command {
void execute();
}
今は、受信者が実行する全ての異なる種類のアクションに対して実装を作成する必要があります。3つのアクションがあるため、3つのコマンドの実装を作成します。それぞれのコマンドの実装は、リクエストを受信者の適切なメソッドに転送します。
package com.scdev.design.command;
public class OpenFileCommand implements Command {
private FileSystemReceiver fileSystem;
public OpenFileCommand(FileSystemReceiver fs){
this.fileSystem=fs;
}
@Override
public void execute() {
//open command is forwarding request to openFile method
this.fileSystem.openFile();
}
}
package com.scdev.design.command;
public class CloseFileCommand implements Command {
private FileSystemReceiver fileSystem;
public CloseFileCommand(FileSystemReceiver fs){
this.fileSystem=fs;
}
@Override
public void execute() {
this.fileSystem.closeFile();
}
}
package com.scdev.design.command;
public class WriteFileCommand implements Command {
private FileSystemReceiver fileSystem;
public WriteFileCommand(FileSystemReceiver fs){
this.fileSystem=fs;
}
@Override
public void execute() {
this.fileSystem.writeFile();
}
}
今は受信者とコマンドの実装が準備できているので、インボーカークラスの実装へ移ることができます。
コマンドパターンのインボーカークラス
インボーカーは、Commandをカプセル化してリクエストをコマンドオブジェクトに渡し、処理を行うシンプルなクラスです。
package com.scdev.design.command;
public class FileInvoker {
public Command command;
public FileInvoker(Command c){
this.command=c;
}
public void execute(){
this.command.execute();
}
}
我々のファイルシステムユーティリティの実装は完了し、シンプルなコマンドパターンのクライアントプログラムを作成する準備ができました。しかし、その前に、適切なFileSystemReceiverオブジェクトを作成するためのユーティリティメソッドを提供します。オペレーティングシステムの情報を取得するためにSystemクラスを使用できるため、それを利用するか、入力に基づいて適切なタイプを返すためにファクトリーパターンを使用することができます。
package com.scdev.design.command;
public class FileSystemReceiverUtil {
public static FileSystemReceiver getUnderlyingFileSystem(){
String osName = System.getProperty("os.name");
System.out.println("Underlying OS is:"+osName);
if(osName.contains("Windows")){
return new WindowsFileSystemReceiver();
}else{
return new UnixFileSystemReceiver();
}
}
}
では、私たちのファイルシステムユーティリティを利用するコマンドパターンのクライアントプログラムの作成に移りましょう。
package com.scdev.design.command;
public class FileSystemClient {
public static void main(String[] args) {
//Creating the receiver object
FileSystemReceiver fs = FileSystemReceiverUtil.getUnderlyingFileSystem();
//creating command and associating with receiver
OpenFileCommand openFileCommand = new OpenFileCommand(fs);
//Creating invoker and associating with Command
FileInvoker file = new FileInvoker(openFileCommand);
//perform action on invoker object
file.execute();
WriteFileCommand writeFileCommand = new WriteFileCommand(fs);
file = new FileInvoker(writeFileCommand);
file.execute();
CloseFileCommand closeFileCommand = new CloseFileCommand(fs);
file = new FileInvoker(closeFileCommand);
file.execute();
}
}
クライアントは適切なタイプのコマンドオブジェクトを作成する責任があることに注意してください。たとえば、ファイルを書き込みたい場合、CloseFileCommandオブジェクトを作成するべきではありません。クライアントプログラムはまた、レシーバーをコマンドに接続し、コマンドをインボーカークラスに接続する責任もあります。上記のコマンドパターンの例プログラムの出力は次の通りです。
Underlying OS is:Mac OS X
Opening file in unix OS
Writing file in unix OS
Closing file in unix OS
コマンドパターンのクラスダイアグラム
私たちのファイルシステムユーティリティ実装のクラス図、こちらです。
コマンドパターンの重要なポイント
- Command is the core of command design pattern that defines the contract for implementation.
- Receiver implementation is separate from command implementation.
- Command implementation classes chose the method to invoke on receiver object, for every method in receiver there will be a command implementation. It works as a bridge between receiver and action methods.
- Invoker class just forward the request from client to the command object.
- Client is responsible to instantiate appropriate command and receiver implementation and then associate them together.
- Client is also responsible for instantiating invoker object and associating command object with it and execute the action method.
- Command design pattern is easily extendible, we can add new action methods in receivers and create new Command implementations without changing the client code.
- The drawback with Command design pattern is that the code gets huge and confusing with high number of action methods and because of so many associations.
コマンドデザインパターンのJDKの例を日本語で言い換えると、
「Runnable」インターフェース(java.lang.Runnable)と「Swing Action」(javax.swing.Action)は、コマンドパターンを使用しています。