Ir al contenido

Command (patrón de diseño)

De Wikipedia, la enciclopedia libre

En programación orientada a objetos, Command es un patrón de diseño.

Intención

[editar]

Este patrón permite solicitar una operación a un objeto sin conocer realmente el contenido de esta operación, ni el receptor real de la misma. Para ello se encapsula la petición como un objeto, con lo que además facilita la parametrización de los métodos.

Propósito

[editar]
  • Encapsula un mensaje como un objeto, con lo que permite gestionar colas o registro de mensaje y deshacer operaciones.
  • Soportar restaurar el estado a partir de un momento dado.
  • Ofrecer una interfaz común que permita invocar las acciones de forma uniforme y extender el sistema con nuevas acciones de forma más sencilla.

Motivo

[editar]
  • El concepto de "orden" puede ser ambiguo y complejo en los sistemas actuales y al mismo tiempo muy extendido: intérpretes de órdenes del sistema operativo, lenguajes de macros de paquetes ofimáticos, gestores de bases de datos, protocolos de servidores de Internet, etc.
  • Este patrón presenta una forma sencilla y versátil de implementar un sistema basado en comandos facilitándose su uso y ampliación.

Aplicaciones

[editar]
  • Facilitar la parametrización de las acciones a realizar.
  • Independizar el momento de petición del de ejecución y abstrayendo/desacoplando a estos elementos de la implementación de la orden.
  • Implementar CallBacks, especificando que órdenes queremos que se ejecuten en ciertas situaciones de otras órdenes. Es decir, un parámetro de una orden puede ser otra orden a ejecutar.
  • Soportar el "deshacer".
  • Desarrollar sistemas utilizando órdenes de alto nivel que se construyen con operaciones sencillas (primitivas).

Estructura

[editar]

Terminología

[editar]

La terminología usada para describir la implementación del patrón orden (command pattern) no es consistente y puede por tanto ser confusa. Esto resulta de la ambigüedad, el uso de sinónimos e implementaciones que puede oscurecer el patrón original por ir más allá de él.

  1. Ambigüedad.
    1. El término orden es ambiguo. Por ejemplo: mover arriba; mover arriba puede referirse a una orden simple (mover arriba) que debe ejecutarse por dos veces, o puede referirse a dos órdenes, cada una de las cuales ocurre que hace lo mismo (mover arriba). Si la primera versión de la orden es añadida por dos veces en una pila de retrotracción (undo stack), ambos items en la pila se refieren a la misma instancia de la orden. Esto puede ser apropiado cuando un comando puede ser siempre retrotraído del mismo modo(p.ej: mover abajo). Tanto la Banda de los cuatro (Gang of Four) como el ejemplo Java de aquí abajo usan esta interpretación del término orden. Por otro lado, si la interpretación posterior es lo que se añade a la pila de retrotracción, la pila se refiere a dos objetos separados. Esto puede ser apropiado cuando cada objeto en la pila debe contener información que permita al comando ser retrotraído (deshecho). Por ejemplo, para retrotraer una orden de borrar selección, la orden debe contener una copia del texto eliminado tal que pueda ser reinsertado si la orden de borrar selección debe ser retrotraída. Nótese que usar un objeto separado por cada invocación de la orden es también un ejemplo del patrón cadena de responsabilidades.
    2. El término ejecutar también es ambiguo. Se puede referir a la ejecución del código identificado por el método ejecutar del objeto orden. Sin embargo en Windows Presentation Foundation de Microsoft una orden se considera ejecutada cuando el método ejecutar de la orden ha sido invocado, pero no significa necesariamente que el código de la aplicación haya sido ejecutado. Eso ocurre solo tras algún procesado de eventos más.
  2. Sinónimos y homónimos.
    1. Cliente(Client), Fuente(Source), Invocador (Invoker): el botón, el botón de barra de herramientas, o ítem de menú clicado, el atajo de teclado presionado por el usuario.
    2. Objeto Orden(Command Object), Objeto Orden Enrutada (Routed Command Object), Objeto Acción (Action Object): un objeto (p.ej: un objeto OrdenCopiar (CopyCommand)), el cual conoce acerca de teclas de atajo, imágenes de botón, texto de orden, etc. relacionados con la orden. Un objeto Fuente/Invocador llama al método ejecutar/realizarAcción del objeto Orden/Acción. El objeto Orden/Acción notifica a los objetos Fuentes/Invocadores apropiados cuando la disponibilidad de una Orden/Acción ha cambiado. Esto permite a los botones e items de menú inactivarse (en color gris) cuando una Orden/Acción no puede ser ejecutada/realizada.
    3. Receptor(Receiver), Objeto Destino(Target Object): el objeto que está a punto de ser copiado, pegado movido, etc. El objeto Receptor posee el método que es llamado en el método ejecutar de la orden. El receptor es típicamente el Objeto Destino. Por ejemplo, si el objeto Receptor es un cursor y el método se llama moverArriba, se esperaría que el cursor es el objeto de tal acción moverArriba. Por otro lado, si el código está definido por el objeto Orden mismo, el objeto Destino será otro objeto completamente diferente.
    4. Objeto Orden (Command Object), Argumentos de Evento Enrutado (routed event args), Objeto Evento (event object): el objeto que es pasado de la Fuente al objeto Orden/Acción, al objeto Destino al código que hace el trabajo. Cada click de botón o atajo de teclado resulta en un nuevo objeto Orden/Evento. Algunas implementaciones añaden más información (p.ej: la Sección de un documento) al objeto Orden/Evento cuando es pasado de un objeto (p.ej: OrdenCopiar) a otro. Otras implementaciones ponen objetos órdenes/Eventos en otros objetos Eventos (como pequeñas cajas dentro de cajas mayores) según se van moviendo por la línea de ejecución, para evitar conflictos de nombramientos. (Véase también patrón cadena de responsabilidades).
    5. Manipulador(Handler), ManipuladorDeEventoEnrutadoEjecutado(ExecutedRoutedEventHandler), método(method), función(function): el código real que hace el copiado, pegado, movida, etc. En algunas implementaciones el código del manipulador es parte del objeto Orden/Acción. En otras implementaciones es parte del objeto Receptor/Destino, y en aun otras implementaciones el código del manipulador es guardado separado de otros objetos.
    6. Gestor de órdenes (Command Manager), Gestor de Retrotracción (Undo Manager), Planificador (Scheduler), Cola (Queue), Resolutor(Dispatcher), Invocador(Invoker): un objeto que pone objetos Orden/Evento en una pila de retrotracción o en otra pila de repetición, o que mantiene objetos Orden/Evento hasta que otros objetos estén preparados para actuar con ellos, o que enruta objetos de Orden/Evento al objeto Receptor/Destino o código manipulador apropiado.
  3. Implementaciones que exceden el patrón Orden original.
    1. Windows Presentation Foundation (WPF) de Microsoft, presenta órdenes enrutadas, que combinan el patrón Orden con el procesado de eventos. Como resultado el objeto Orden ya no contiene una referencia al objeto Destino ni una referencia al código de aplicación. En cambio, invocar el método ejecutar del objeto Orden resulta en algo llamado Evento Enrutado Ejecutado (Executed Routed Event) el cual durante el tunelado o enburbujamiento del evento puede encontrar un objeto unión (binding) que identifica el Destino u el código de aplicación, el cual se ejecutará entonces.

Ejemplo

[editar]

Patrón Comando en Wikibooks

Considérese un "simple" interruptor. En este ejemplo configuramos el interruptor con dos órdenes: encender la luz y apagar la luz. Después, se va a hacer una pequeña refactorización para añadir el beneficio al usar las abstracciones correctas en los Command para hacer que el interruptor puede ser usado en cualquier dispositivo no solo con una luz. Como se va a ver en el siguiente ejemplo, el interruptor enciende y apaga la luz, pero el constructor del interruptor es capaz de aceptar cualquier subclase de Orden para sus dos parámetros y con una fácil abstracción, el último ejemplo modelará el interruptor para funcionar sobre cualquier dispositivo capaz de encenderse o apagarse como un motor, por ejemplo.

Implementación en Java

[editar]
/* The Invoker class */
public class Switch {

    private Command flipUpCommand;
    private Command flipDownCommand;

    public Switch(Command flipUpCmd, Command flipDownCmd) {
         this.flipUpCommand = flipUpCmd;
         this.flipDownCommand = flipDownCmd;
    }

    public void flipUp() {
         flipUpCommand.execute();
    }

    public void flipDown() {
         flipDownCommand.execute();
    }
}

/* The Receiver class */
public class Light {

     public Light() {  }

     public void turnOn() {
        System.out.println("The light is on");
     }

     public void turnOff() {
        System.out.println("The light is off");
     }
}

/* The Command interface */
public interface Command {
    void execute();
}

/* The Command for turning the light on in North America, or turning the light off in most other places */
public class FlipUpCommand implements Command {

   private Light theLight;

   public FlipUpCommand(Light light) {
        this.theLight=light;
   }

   public void execute(){
      theLight.turnOn();
   }
}

/* The Command for turning the light off in North America, or turning the light on in most other places */
public class FlipDownCommand implements Command {

   private Light theLight;

   public FlipDownCommand(Light light) {
        this.theLight=light;
   }

   public void execute() {
      theLight.turnOff();
   }
}

/* The test class or client */
public class PressSwitch {

   public static void main(String[] args){
      // Check number of arguments
      if (args.length != 1) {
         System.err.println("Argument \"ON\" or \"OFF\" is required.");
         System.exit(-1);
      }

      Light lamp = new Light();
      Command switchUp = new FlipUpCommand(lamp);
      Command switchDown = new FlipDownCommand(lamp);

      // See criticism of this model above:
      // The switch itself may not be aware of the specific receiver details (the lamp)
      Switch mySwitch = new Switch(switchUp, switchDown);

      switch (args[0]) {
         case "ON":
            mySwitch.flipUp();
            break;
         case "OFF":
            mySwitch.flipDown();
            break;
         default:
            System.err.println("Argument \"ON\" or \"OFF\" is required.");
            System.exit(-1);
      }
   }
}

Una crítica de la aplicación de ejemplo es que no modeliza verdaderamente un circuito eléctrico. Un interruptor eléctrico es tonto. Un verdadero interruptor binario solo conoce si está en una posición u otra. Nótese que el ejemplo es perfectamente válido para modelar un interruptor que solo entiende/funciona con lámparas pero el uso del patrón Command nos da la suficiente abstracción para que no se limite solo a eso.

Así, una primera solución para un modelo más general sería:

  • Que el interruptor no debe saber nada o no tiene relación directa con las tareas que se le podrían asignar en un circuito. El interruptor simplemente publica un evento de su estado actual (encendido o apagado). El circuito debería entonces contener una Máquina de estados que gestione los estados en momentos dados (escuchando los eventos del interruptor) para acomodar apropiadamente complejos circuitos con tareas e interruptores. Cada tarea es condicional a un estado específico del circuito (no directamente a un interruptor). En conclusión, el interruptor no debería ser consciente de los detalles de la lámpara.
Esta solución es válida y genera código limpio pero aporta más complejidad al patrón añadiendo capas/participantes e incluso cambiando las atribuciones de ciertos de ellos:
    • El uso de eventos y su gestión excede lo que pretende abarcar el patrón por lo que si el uso de patrones pretende simplificar la implementación de una solución, este añadido va en contra de ello.
    • El participante Invoker (Switch), según lo indicado en el diagrama y su detalle, debe crear/tener la instancia de los comandos para activar su ejecución por lo que el interruptor no debería modelarse como ser un simple sistema binario con dos estados que solo lance eventos.

Así, la posible solución que sigue el planteamiento del patrón de una forma más simple sería proveer la abstracción para que tanto los comandos concretos (como indirectamente el interruptor) manejara elementos capaces de encenderse/apagarse. Esto se consigue con estos simples cambios en el código original del ejemplo:

/* An interface that defines actions that the receiver can perform */
public interface ISwitchable {
    void turnOn();
    void turnOff();
}

/* The Receiver class */
public class Light implements ISwitchable {

     public Light() {  }

     public void turnOn() {
        System.out.println("The light is on");
     }

     public void turnOff() {
        System.out.println("The light is off");
     }
}
/* The Command for turning the light on in North America, or turning the light off in most other places */
public class FlipUpCommand implements Command {

   private final ISwitchable switchableReceiver;

   public FlipUpCommand(ISwitchable switchableReceiver) {
        this.switchableReceiver = switchableReceiver;
   }

   public void execute(){
      switchableReceiver.turnOn();
   }
}

/* The Command for turning the light off in North America, or turning the light on in most other places */
public class FlipDownCommand implements Command {

   private final ISwitchable switchableReceiver;

   public FlipDownCommand(ISwitchable switchableReceiver) {
        this.switchableReceiver = switchableReceiver;
   }

   public void execute() {
      switchableReceiver.turnOff();
   }
}

Ninguno de los otros participantes: Command y Client (CommandManager) tiene que cambiarse.


Participantes

[editar]
  • AbstractCommand.

Clase que ofrece una interfaz para la ejecución de órdenes. Define los métodos do y undo que se implementarán en cada clase concreta.

  • ConcreteCommand.

Clase que implementa una orden concreta y sus métodos do y undo. Su constructor debe inicializar los parámetros de la orden.

  • Invoker.

Clase que instancia las órdenes, puede a su vez ejecutarlas inmediatamente (llamando a do) o dejar que el CommandManager lo haga.

  • CommandManager.

Responsable de gestionar una colección de objetos orden creadas por el Invoker. llamará a los métodos do y unDo. Gestionará su secuenciación y reordenación (sobre la base de prioridades por ejemplo).

Consecuencias

[editar]
  • Se independiza la parte de la aplicación que invoca las órdenes de la implementación de los mismos.
  • Al tratarse las órdenes como objetos, se puede realizar herencia de las mismas, composiciones de órdenes (mediante el patrón Composite).
  • Se facilita la ampliación del conjunto de órdenes.

Usos conocidos

[editar]

Las clases Button y MenuItem de Java facilitan la utilización de este patrón, declaran los métodos getActionCommand y setActionCommand para dar nombres a las acciones realizadas por los objetos, facilitándose una correspondencia entre ambos.

Patrones relacionados

[editar]

Ofrece una forma alternativa de llamar a los órdenes además del uso del command manager.

Se puede implementar un pequeño Intérprete mediante clases Command.

Puede servir para implementar la lógica de Deshacer de forma un tanto automática o a alto nivel.

Permite realizar agrupaciones de órdenes de forma similar a una macro.

Hay quien lo utiliza para implementar la copia de la orden al histórico de órdenes.

Puede mantener el estado que requiere el comando para deshacer su efecto.