Design Patterns - Command

Propósito

El patrón Command (comando, orden) encapsula una petición en un objeto. Esto permite parametrizar métodos con diferentes solicitudes, retrasar o poner en cola la ejecución de una solicitud, y soportar operaciones de deshacer.

Suena complicado?? Bueno, es mucho más simple… supongamos que queremos ejecutar una acción (login de usuario), lo que hacemos es crear un objeto comando que tenga la información necesaria para que el manejador (handler) pueda realizar esta tarea.

Problema

El patrón Command resuelve varios problemas:

  • Acoplamiento entre Emisores y Receptores: Cuando el emisor de una solicitud (como un botón en una interfaz de usuario) está directamente acoplado a la lógica que debe ejecutar.
  • Extensibilidad y Reutilización: Facilita agregar nuevas solicitudes sin cambiar las clases existentes.

Solución

El patrón Command propone las siguientes soluciones:

  • Objetos de Comando: Cada solicitud se encapsula en un objeto de comando que contiene toda la información necesaria para ejecutar la acción.
  • Separación entre Emisores y Receptores: Los emisores de solicitudes no necesitan conocer los detalles de cómo se manejan las solicitudes.

Estructura

command

Participantes

  • Command: La interfaz que define el método execute().
  • ConcreteCommand: Implementa la interfaz Command, invoca la operación en el Receiver. Define un enlace entre un objeto Receiver y una acción.
  • Invoker: Mantiene una referencia al objeto Command y llama a su método execute().
  • Receiver: Realiza el trabajo cuando el método execute() del Command es llamado. Es el objeto que sabe cómo realizar las operaciones.
  • Client: Crea y configura los objetos ConcreteCommand, establece su Receiver.

Cuándo Usarlo

Este patrón es recomendable cuando:

  • debemos parametrizar objetos según una acción a realizar.
  • necesitamos implementar operaciones de deshacer y rehacer.
  • queremos mantener un historial de solicitudes.

Ventajas

Desacoplamiento: Reduce el acoplamiento entre la clase que invoca la operación y la que sabe cómo ejecutarla.

Extensibilidad: Facilita agregar nuevos comandos sin cambiar el código existente.

Flexibilidad: Permite realizar operaciones más complejas, como colas de comandos y operaciones deshacer/rehacer.

Desventajas

Complejidad Adicional: Puede aumentar la complejidad del código al introducir varias clases y objetos.

Sobrecarga: Si cada acción simple se convierte en un comando, puede resultar en una sobrecarga de clases y objetos.

Ejemplo: Sistema Automatización Hogar

Debemos crear un sistema de automatización del hogar, que permite a los usuarios controlar una variedad de dispositivos electrónicos del hogar, como luces, termostatos y sistemas de seguridad, a través de una interfaz de usuario centralizada.

Problema

Necesitamos enviar comandos a los dispositivos de una manera flexible y extensible.

Los comandos pueden variar en su complejidad y pueden requerir acciones como activar, desactivar, ajustar la temperatura, o establecer modos de seguridad.

Además, el sistema debe ser capaz de soportar nuevos tipos de comandos en el futuro sin modificar el código central de la aplicación.

Solución planteada

Elegimos usar el patrón Command porque nos permite encapsular todas las solicitudes en objetos de comando individuales. Esto no solo permite la parametrización y la ejecución de comandos, sino que también facilita la adición de nuevos comandos a medida que se introducen nuevos dispositivos y funcionalidades.

Definimos la interfaz Command que representa las acciones ejecutables como objetos. Creamos clases concretas LightOnCommand y LightOffCommand que implementan Command y encapsulan la funcionalidad de encender y apagar las luces, actuando como comandos específicos. Un Receiver, la clase Light, es el objeto que tiene la lógica de negocio para realizar las acciones. Un Invoker, representado por la clase RemoteControl, es responsable de iniciar los comandos. Finalmente, el cliente, la clase SmartHomeApp, crea los comandos y los asigna al invocador para que sean ejecutados.

command

Código Java

Codificamos en Java lo que preparamos en el diagrama.

Creamos la Interfaz Command:


  
  interface Command {
      void execute();
  }
              
              

Implementamos los ConcreteCommands:


              
  // Comandos para controlar las luces
  class LightOnCommand implements Command {
      private Light light;
  
      public LightOnCommand(Light light) {
          this.light = light;
      }
  
      public void execute() {
          light.turnOn();
      }
  }
  
  class LightOffCommand implements Command {
      private Light light;
  
      public LightOffCommand(Light light) {
          this.light = light;
      }
  
      public void execute() {
          light.turnOff();
      }
  }
  
  // Otros comandos concretos para diferentes dispositivos pueden ser definidos aquí
              
              

Definimos el Receiver:


              
  class Light {
      public void turnOn() {
          System.out.println("La luz está encendida.");
      }
  
      public void turnOff() {
          System.out.println("La luz está apagada.");
      }
  }
  
  // Clases adicionales para otros dispositivos pueden ser definidas aquí
              
              

Creamos el Invoker:


              
  class RemoteControl {
      private Command command;
  
      public void setCommand(Command command) {
          this.command = command;
      }
  
      public void pressButton() {
          command.execute();
      }
  }
              
              

El código cliente:


              
  public class SmartHomeApp {
      public static void main(String[] args) {
          Light livingRoomLight = new Light();
          Command lightOn = new LightOnCommand(livingRoomLight);
          Command lightOff = new LightOffCommand(livingRoomLight);
  
          RemoteControl remote = new RemoteControl();
          remote.setCommand(lightOn);
          remote.pressButton();
  
          remote.setCommand(lightOff);
          remote.pressButton();
      }
  }
              
              

Mapeo (del ejemplo a Participantes)

Los participantes que vimos antes son: Command, ConcreteCommand, Receiver, Invoker, Client:

  • Command (Command): Interfaz que declara el método execute(), que es el punto de entrada para ejecutar comandos
  • LightOnCommand, LightOffCommand (ConcreteCommand): Clases concretas de comando que, cuando se ejecuta, enciende/apaga la luz usando el método turnOn()/turnOff() del receptor Light.
  • Light (Receiver): Contiene la lógica de negocio real, los métodos para encender y apagar la luz.
  • RemoteControl (Invoker): Mantiene una referencia a un comando y puede invocarlo mediante su método pressButton().
  • SmartHomeApp (Client): Crea y configura los comandos concretos y el invocador, y luego inicia la ejecución de los comandos.

Conclusiones

Este ejemplo muestra cómo usar el patrón Command para crear un sistema de automatización del hogar. Este diseño facilita la extensión del sistema para incluir más tipos de comandos y dispositivos, manteniendo un acoplamiento débil entre el emisor de la solicitud y el receptor, el que realiza la acción.

Patrones relacionados

  • Memento

Puede ser usado para mantener el estado necesario para deshacer operaciones.

  • Composite

Los comandos pueden ensamblarse en estructuras compuestas para operaciones más complejas.