Command Design Pattern

コマンドパターンは、行動デザインパターンの一つです。コマンドデザインパターンは、リクエスト-レスポンスモデルにおける疎結合の実装に使用されます。

コマンドパターン

コマンドパターンでは、リクエストがinvokerに送信され、invokerはそれをカプセル化されたcommandオブジェクトに渡します。コマンドオブジェクトは、特定のアクションを実行するためにReceiverの適切なメソッドにリクエストを渡します。クライアントプログラムは、レシーバーオブジェクトを作成し、それをコマンドにアタッチします。次に、インボーカーオブジェクトを作成し、コマンドオブジェクトをアタッチしてアクションを実行します。クライアントプログラムがアクションを実行するとき、コマンドとレシーバーオブジェクトに基づいて処理されます。

コマンドデザインパターンの例

実際のシナリオを見て、Commandパターンを実装する方法を見てみましょう。たとえば、ファイルシステムユーティリティを提供し、ファイルを開き、書き込み、閉じるメソッドを提供したいとします。このファイルシステムユーティリティは、WindowsやUnixなどの複数のオペレーティングシステムをサポートする必要があります。ファイルシステムユーティリティを実装するには、まず、実際にすべての作業を行うレシーバークラスを作成する必要があります。私たちはJavaのインターフェースの用語でコードを記述するので、FileSystemReceiverインターフェースと、Windows、Unix、Solarisなどの異なるオペレーティングシステムフレーバー用の実装クラスを持つことができます。

コマンドパターンのレシーバークラス

package com.journaldev.design.command;

public interface FileSystemReceiver {

	void openFile();
	void writeFile();
	void closeFile();
}

FileSystemReceiverインターフェースは、実装クラスのための契約を定義します。単純化のために、UnixとWindowsシステムで動作する2つのフレーバーのレシーバークラスを作成しています。

package com.journaldev.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.journaldev.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 annotationsoverride annotation benefitsをお読みください。これで、レシーバークラスが準備できたので、コマンドクラスを実装できるようになりました。

コマンドパターンのインターフェースと実装

基本コマンドを作成するために、インターフェースまたは抽象クラスを使用できます。これは設計上の決定であり、要件に依存します。デフォルトの実装がないため、インターフェースを選択します。

package com.journaldev.design.command;

public interface Command {

	void execute();
}

今、受信者によって実行されるさまざまな種類のアクションの実装を作成する必要があります。3つのアクションがあるため、3つのコマンドの実装を作成します。各コマンドの実装は、要求を受信者の適切なメソッドに転送します。

package com.journaldev.design.command;

public class OpenFileCommand implements Command {

	private FileSystemReceiver fileSystem;
	
	public OpenFileCommand(FileSystemReceiver fs){
		this.fileSystem=fs;
	}
	@Override
	public void execute() {
		// openコマンドは、openFileメソッドに要求を転送しています。
		this.fileSystem.openFile();
	}

}
package com.journaldev.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.journaldev.design.command;

public class WriteFileCommand implements Command {

	private FileSystemReceiver fileSystem;
	
	public WriteFileCommand(FileSystemReceiver fs){
		this.fileSystem=fs;
	}
	@Override
	public void execute() {
		this.fileSystem.writeFile();
	}

}

これで、受信者とコマンドの実装が準備できたので、インボーカークラスを実装できます。

コマンドパターンのインボーカークラス

インボーカーは、コマンドをカプセル化し、リクエストをコマンドオブジェクトに渡して処理させるシンプルなクラスです。

package com.journaldev.design.command;

public class FileInvoker {

	public Command command;
	
	public FileInvoker(Command c){
		this.command=c;
	}
	
	public void execute(){
		this.command.execute();
	}
}

私たちのファイルシステムユーティリティの実装が完了し、簡単なコマンドパターンクライアントプログラムの作成に移ることができます。しかし、その前に、適切なFileSystemReceiverオブジェクトを作成するためのユーティリティメソッドを提供します。オペレーティングシステム情報を取得するためにSystemクラスを使用できるので、これを使用します。それとは別に、入力に基づいて適切なタイプを返すためにFactoryパターンを使用することもできます。

package com.journaldev.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.journaldev.design.command;

public class FileSystemClient {

	public static void main(String[] args) {
		//レシーバーオブジェクトの作成
		FileSystemReceiver fs = FileSystemReceiverUtil.getUnderlyingFileSystem();
		
		//コマンドの作成とレシーバーとの関連付け
		OpenFileCommand openFileCommand = new OpenFileCommand(fs);
		
		//インボーカーの作成とコマンドとの関連付け
		FileInvoker file = new FileInvoker(openFileCommand);
		
		//インボーカーオブジェクトでのアクションの実行
		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

コマンドパターンクラス図

こちらは、ファイルシステムユーティリティの実装のためのクラス図です。

コマンドパターンの重要なポイント

  • コマンドは、実装のための契約を定義するコマンドデザインパターンの核心です。
  • 受信者の実装は、コマンドの実装とは別です。
  • コマンドの実装クラスは、受信者オブジェクトで呼び出すメソッドを選択します。受信者のメソッドごとにコマンドの実装があります。これは、受信者とアクションメソッドの間の橋渡しとして機能します。
  • Invokerクラスは、クライアントからコマンドオブジェクトへのリクエストをただ転送するだけです。
  • クライアントは、適切なコマンドと受信者の実装をインスタンス化し、それらを関連付ける責任があります。
  • クライアントはまた、Invokerオブジェクトをインスタンス化し、コマンドオブジェクトをそれと関連付けてアクションメソッドを実行する責任があります。
  • コマンドデザインパターンは拡張性が高く、新しいアクションメソッドを受信者に追加し、クライアントコードを変更せずに新しいコマンドの実装を作成できます。
  • コマンドデザインパターンの欠点は、アクションメソッドの数が多くなるとコードが複雑になり、多くの関連が生じることです。

コマンドデザインパターンJDKの例

Runnableインターフェース(java.lang.Runnable)およびSwingアクション(javax.swing.Action)は、コマンドパターンを使用します。

Source:
https://www.digitalocean.com/community/tutorials/command-design-pattern