El Patrón de Comando es uno de los Patrones de Diseño de Comportamiento. El patrón de diseño de comando se utiliza para implementar un acoplamiento flexible en un modelo de solicitud-respuesta.
Patrón de Comando
En el patrón de comando, la solicitud se envía al
invocador
y el invocador la pasa al objeto comando
encapsulado. El objeto de comando pasa la solicitud al método apropiado de Receptor
para realizar la acción específica. El programa del cliente crea el objeto receptor y luego lo adjunta al comando. Luego crea el objeto invocador y adjunta el objeto de comando para realizar una acción. Ahora, cuando el programa del cliente ejecuta la acción, se procesa en función del comando y el objeto receptor.
Ejemplo de Patrón de Diseño de Comando
Vamos a analizar un escenario de la vida real donde podemos implementar el patrón de Comando. Supongamos que queremos proporcionar una utilidad de sistema de archivos con métodos para abrir, escribir y cerrar archivos. Esta utilidad de sistema de archivos debería ser compatible con varios sistemas operativos como Windows y Unix. Para implementar nuestra utilidad de sistema de archivos, primero necesitamos crear las clases receptoras que realizarán todo el trabajo. Dado que codificamos en términos de interfaz en Java, podemos tener la interfaz FileSystemReceiver
y sus clases de implementación para diferentes sabores de sistemas operativos, como Windows, Unix, Solaris, etc.
Clases Receptoras del Patrón de Comando
package com.journaldev.design.command;
public interface FileSystemReceiver {
void openFile();
void writeFile();
void closeFile();
}
La interfaz FileSystemReceiver define el contrato para las clases de implementación. Para simplificar, estoy creando dos versiones de clases receptoras para trabajar con sistemas Unix y 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");
}
}
¿Notaste la anotación Override y si te preguntas por qué se usa, por favor, lee anotaciones en Java y beneficios de la anotación override. Ahora que nuestras clases receptoras están listas, podemos pasar a implementar nuestras clases de Comando.
Interfaz y Implementaciones del Patrón de Comando
Podemos utilizar una interfaz o clase abstracta para crear nuestro Comando base; es una decisión de diseño que depende de sus requisitos. Optamos por la interfaz porque no tenemos implementaciones predeterminadas.
package com.journaldev.design.command;
public interface Command {
void execute();
}
Ahora necesitamos crear implementaciones para todos los diferentes tipos de acciones realizadas por el receptor. Dado que tenemos tres acciones, crearemos tres implementaciones de Comando. Cada implementación de Comando enviará la solicitud al método correspondiente del 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() {
//El comando de apertura está enviando la solicitud al 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();
}
}
Ahora que tenemos el receptor y las implementaciones de comando listos, podemos pasar a implementar la clase invocadora.
Clase Invocadora del Patrón de Comando
La invocadora es una clase simple que encapsula el Comando y pasa la solicitud al objeto de comando para procesarla.
package com.journaldev.design.command;
public class FileInvoker {
public Command command;
public FileInvoker(Command c){
this.command=c;
}
public void execute(){
this.command.execute();
}
}
Nuestra implementación de utilidad del sistema de archivos está lista y podemos pasar a escribir un programa cliente simple de patrón de comando. Pero antes de eso, proporcionaré un método de utilidad para crear el objeto FileSystemReceiver
adecuado. Dado que podemos usar la clase Sistema para obtener información del sistema operativo, utilizaremos esto o de lo contrario podemos usar el patrón Factory para devolver el tipo apropiado según la 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();
}
}
}
Ahora pasemos a crear nuestro programa cliente de ejemplo de patrón de comando que consumirá nuestra utilidad del sistema de archivos.
package com.journaldev.design.command;
public class FileSystemClient {
public static void main(String[] args) {
//Creando el objeto receptor
FileSystemReceiver fs = FileSystemReceiverUtil.getUnderlyingFileSystem();
//creando comando y asociándolo con el receptor
OpenFileCommand openFileCommand = new OpenFileCommand(fs);
//Creando el invocador y asociándolo con el comando
FileInvoker file = new FileInvoker(openFileCommand);
//realizar acción en 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 el cliente es responsable de crear el tipo apropiado de objeto de comando. Por ejemplo, si desea escribir un archivo, no se supone que debe crear un objeto CloseFileCommand
. El programa cliente también es responsable de adjuntar el receptor al comando y luego el comando a la clase invocadora. La salida del programa de ejemplo de patrón de comando anterior es:
Underlying OS is:Mac OS X
Opening file in unix OS
Writing file in unix OS
Closing file in unix OS
Diagrama de Clase del Patrón de Comando
Aquí está el diagrama de clases para nuestra implementación de utilidad del sistema de archivos.
Puntos Importantes del Patrón de Comando
- El comando es el núcleo del patrón de diseño de comando que define el contrato para la implementación.
- La implementación del receptor está separada de la implementación del comando.
- Las clases de implementación del comando eligen el método a invocar en el objeto receptor, para cada método en el receptor habrá una implementación del comando. Funciona como un puente entre el receptor y los métodos de acción.
- La clase Invoker solo reenvía la solicitud del cliente al objeto comando.
- El cliente es responsable de instanciar el comando y la implementación del receptor apropiados y luego asociarlos entre sí.
- El cliente también es responsable de instanciar el objeto invocador y asociar el objeto comando con él y ejecutar el método de acción.
- El patrón de diseño de comando es fácilmente extensible, podemos agregar nuevos métodos de acción en los receptores y crear nuevas implementaciones de comando sin cambiar el código del cliente.
- La desventaja del patrón de diseño de comando es que el código se vuelve enorme y confuso con un alto número de métodos de acción y debido a tantas asociaciones.
Ejemplo de Patrón de Diseño de Comando en JDK
La interfaz Runnable (java.lang.Runnable) y la Acción de Swing (javax.swing.Action) utilizan el patrón de comando.
Source:
https://www.digitalocean.com/community/tutorials/command-design-pattern