Padrão de Comando
No padrão de comando, a solicitação é enviada ao invocador e este a repassa ao objeto de comando encapsulado. O objeto de comando passa a solicitação para o método apropriado do objeto de receptor para realizar a ação específica. O programa cliente cria o objeto de receptor e o anexa ao comando. Em seguida, ele cria o objeto invocador e anexa o objeto de comando para executar uma ação. Agora, quando o programa cliente executa a ação, ela é processada com base no comando e no objeto de receptor.
“`plaintext
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 queremos fornecer uma utilidade de sistema de arquivos com métodos para abrir, escrever e fechar arquivos. Esta utilidade de sistema de arquivos deve suportar múltiplos 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 simplificar, estou criando dois sabores 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 Override e se está se perguntando por que ela é usada, por favor, leia sobre anotações Java e os benefícios da anotação de sobreposição. Agora que nossas classes receptoras estão prontas, podemos passar para a implementação de nossas classes de comando.
Interface e Implementações do Padrão de Comando
Podemos usar interface ou classe abstrata para criar nosso Comando base, essa é uma decisão de design e depende dos requisitos. Vamos optar por uma 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ção realizados pelo receptor. Como temos três ações, criaremos três implementações de Comando. Cada implementação de Comando encaminhará a solicitação 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 a solicitação 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 de comando prontas, podemos passar para a implementação da classe invocadora.
Classe Invocadora do Padrão de Comando
O Invocador é uma classe simples que encapsula o Comando e passa a solicitação para o objeto de comando processá-la.
package com.journaldev.design.command;
public class FileInvoker {
public Command command;
public FileInvoker(Command c){
this.command=c;
}
public void execute(){
this.command.execute();
}
}
Nosso implementação de utilitário de sistema de arquivos está pronta e podemos avançar para escrever um programa cliente simples usando o padrão de comando. Mas antes disso, vou fornecer um método de utilitário para criar o objeto apropriado FileSystemReceiver
. Como podemos usar a classe System para obter informações do sistema operacional, vamos usar isso; caso contrário, podemos usar o padrão Factory 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();
}
}
}
Vamos agora criar nosso programa cliente de exemplo usando o padrão de comando que consumirá nosso utilitário 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 o comando e associando com o receptor
OpenFileCommand openFileCommand = new OpenFileCommand(fs);
// Criando o invocador e associando com o comando
FileInvoker file = new FileInvoker(openFileCommand);
// Executando a 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 acima usando o padrão de comando é:
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 da nossa utilidade de sistema de arquivos.
Pontos Importantes do Padrão de Comando
- O Command é o núcleo do padrão de design de comando que define o contrato para a implementação.
- A implementação do Receiver é separada da implementação do Command.
- As classes de implementação do Command escolhem o método a ser invocado no objeto Receiver; para cada método no Receiver, haverá uma implementação do Command. Isso funciona como uma ponte entre o Receiver e os métodos de ação.
- A classe Invoker apenas encaminha a solicitação do cliente para o objeto Command.
- O Cliente é responsável por instanciar o Command apropriado e a implementação do Receiver, e depois associá-los entre si.
- O Cliente também é responsável por instanciar o objeto Invoker e associar o objeto Command a ele, e executar o método de ação.
- O padrão de design Command é facilmente extensível; podemos adicionar novos métodos de ação nos Receivers e criar novas implementações do Command sem alterar o código do cliente.
- A desvantagem do padrão de design Command é 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.
Padrão de Design Command Exemplo JDK
Interface Runnable (java.lang.Runnable) e Swing Action (javax.swing.Action) utilizam o padrão de comando.
Source:
https://www.digitalocean.com/community/tutorials/command-design-pattern