Padrão de Projeto Command

Padrão de Comando é um dos Padrões de Design Comportamental. O padrão de design de comando é usado para implementar o acoplamento flexível em um modelo de solicitação e resposta.

Padrão de Comando

No padrão de comando, a solicitação é enviada ao invoker e o invoker a repassa para o objeto de command encapsulado. O objeto de comando passa a solicitação para o método apropriado do Receiver para realizar a ação específica. O programa cliente cria o objeto receptor e o anexa ao comando. Em seguida, ele cria o objeto invoker e anexa o objeto de comando para realizar uma ação. Agora, quando o programa cliente executa a ação, ela é processada com base no comando e no objeto receptor.

Exemplo de Padrão de Design de Comando

Vamos analisar um cenário da vida real onde podemos implementar o padrão de Comando. Digamos que desejamos fornecer uma utilidade de Sistema de Arquivos com métodos para abrir, escrever e fechar arquivos. Esta utilidade de sistema de arquivos deve suportar vários sistemas operacionais, como Windows e Unix. Para implementar nossa utilidade de Sistema de Arquivos, primeiro precisamos criar as classes receptoras que realmente farão todo o trabalho. Como codificamos em termos de interface em java, podemos ter a interface FileSystemReceiver e suas classes de implementação para diferentes sabores de sistemas operacionais, como Windows, Unix, Solaris etc.

Classes Receptoras do Padrão de Comando

package com.journaldev.design.command;

public interface FileSystemReceiver {

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

A interface FileSystemReceiver define o contrato para as classes de implementação. Para simplicidade, estou criando duas variantes de classes receptoras para trabalhar com sistemas Unix e Windows.

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");
	}

}

Você notou a anotação de Override e se está se perguntando por que ela é usada, por favor, leia anotações em java e benefícios da anotação de override. Agora que nossas classes receptoras estão prontas, podemos passar para a implementação de nossas classes de Comando.

Interface de Padrão de Comando e Implementações

Podemos usar interface ou classe abstrata para criar nosso Comando base, é uma decisão de design e depende dos seus requisitos. Estamos optando por interface porque não temos implementações padrão.

package com.journaldev.design.command;

public interface Command {

	void execute();
}

Agora precisamos criar implementações para todos os diferentes tipos de ações executadas pelo receptor. Como temos três ações, criaremos três implementações de Comando. Cada implementação de Comando encaminhará o pedido para o método apropriado do receptor.

package com.journaldev.design.command;

public class OpenFileCommand implements Command {

	private FileSystemReceiver fileSystem;
	
	public OpenFileCommand(FileSystemReceiver fs){
		this.fileSystem=fs;
	}
	@Override
	public void execute() {
		// O comando de abertura está encaminhando o pedido para o método 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();
	}

}

Agora que temos o receptor e as implementações do comando prontas, podemos passar para a implementação da classe invocadora.

Classe Invocadora do Padrão de Comando

A Invocadora é uma classe simples que encapsula o Comando e passa o pedido para o objeto de comando para processá-lo.

package com.journaldev.design.command;

public class FileInvoker {

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

Nossa implementação de utilidade do sistema de arquivos está pronta e podemos avançar para escrever um programa cliente de padrão de comando simples. Mas antes disso, fornecerei um método de utilidade para criar o objeto FileSystemReceiver apropriado. Como podemos usar a classe Sistema para obter informações do sistema operacional, usaremos isso ou então podemos usar o padrão de fábrica para retornar o tipo apropriado com base na entrada.

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();
		 }
	}
	
}

Agora vamos criar nosso programa cliente de exemplo de padrão de comando que consumirá nossa utilidade de sistema de arquivos.

package com.journaldev.design.command;

public class FileSystemClient {

	public static void main(String[] args) {
		//Criando o objeto receptor
		FileSystemReceiver fs = FileSystemReceiverUtil.getUnderlyingFileSystem();
		
		//criando comando e associando com receptor
		OpenFileCommand openFileCommand = new OpenFileCommand(fs);
		
		//Criando invocador e associando com o comando
		FileInvoker file = new FileInvoker(openFileCommand);
		
		//realizando ação no objeto invocador
		file.execute();
		
		WriteFileCommand writeFileCommand = new WriteFileCommand(fs);
		file = new FileInvoker(writeFileCommand);
		file.execute();
		
		CloseFileCommand closeFileCommand = new CloseFileCommand(fs);
		file = new FileInvoker(closeFileCommand);
		file.execute();
	}

}

Observe que o cliente é responsável por criar o tipo apropriado de objeto de comando. Por exemplo, se você quiser escrever um arquivo, não deve criar o objeto CloseFileCommand. O programa cliente também é responsável por associar o receptor ao comando e, em seguida, o comando à classe invocadora. A saída do programa de exemplo de padrão de comando acima é:

Underlying OS is:Mac OS X
Opening file in unix OS
Writing file in unix OS
Closing file in unix OS

Diagrama de Classe do Padrão de Comando

Aqui está o diagrama de classes para a implementação de nossa utilidade de sistema de arquivos.

Pontos Importantes do Padrão de Comando

  • O comando é o núcleo do padrão de design de comando que define o contrato para a implementação.
  • A implementação do receptor é separada da implementação do comando.
  • As classes de implementação do comando escolhem o método a ser invocado no objeto receptor; para cada método no receptor, haverá uma implementação de comando. Funciona como uma ponte entre o receptor e os métodos de ação.
  • A classe Invoker simplesmente encaminha a solicitação do cliente para o objeto comando.
  • O cliente é responsável por instanciar o comando apropriado e a implementação do receptor e, em seguida, associá-los.
  • O cliente também é responsável por instanciar o objeto invoker e associar o objeto comando a ele, e executar o método de ação.
  • O padrão de design de comando é facilmente extensível; podemos adicionar novos métodos de ação nos receptores e criar novas implementações de comando sem alterar o código do cliente.
  • A desvantagem do padrão de design de comando é que o código fica extenso e confuso com um grande número de métodos de ação e devido a tantas associações.

Exemplo de Padrão de Projeto de Comando em JDK

A interface Runnable (java.lang.Runnable) e a Ação Swing (javax.swing.Action) utilizam o padrão de comando.

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