Patrón de comando
En la programación orientada a objetos, el patrón de comando es un patrón de diseño de comportamiento en el que se utiliza un objeto para encapsular toda la información necesaria para realizar una acción o desencadenar un evento en un momento posterior. Esta información incluye el nombre del método, el objeto que posee el método y los valores de los parámetros del método.
Cuatro términos siempre asociados con el patrón de comando son comando, receptor, invocador y cliente. Un objeto comando sabe acerca del receptor e invoca un método del receptor. Los valores de los parámetros del método del receptor se almacenan en el comando. El objeto receptor para ejecutar estos métodos también se almacena en el objeto de comando por agregación. El receptor luego hace el trabajo cuando se llama al método execute()
en command. Un objeto invocador sabe cómo ejecutar un comando y, opcionalmente, lleva la contabilidad de la ejecución del comando. El invocador no sabe nada sobre un comando concreto, solo sabe sobre el comando interfaz. Los objetos invocadores, objetos de comando y objetos receptores están en manos de un objeto cliente, el cliente decide qué objetos receptores asigna a los objetos de comando y qué comandos asigna al invocador. El cliente decide qué comandos ejecutar en qué puntos. Para ejecutar un comando, pasa el objeto del comando al objeto invocador.
El uso de objetos de comando facilita la construcción de componentes generales que necesitan delegar, secuenciar o ejecutar llamadas a métodos en el momento que elijan sin necesidad de conocer la clase del método o los parámetros del método. El uso de un objeto invocador permite realizar convenientemente la contabilidad sobre las ejecuciones de comandos, así como implementar diferentes modos para los comandos, que son administrados por el objeto invocador, sin la necesidad de que el cliente esté al tanto de la existencia de contabilidad o modos.
Las ideas centrales de este patrón de diseño reflejan fielmente la semántica de las funciones de primera clase y las funciones de orden superior en los lenguajes de programación funcional. Específicamente, el objeto invocador es una función de orden superior de la cual el objeto comando es un argumento de primera clase.
Resumen
El comando El patrón de diseño es uno de los veintitrés patrones de diseño de GoF conocidos que describen cómo resolver problemas de diseño recurrentes para diseñar software orientado a objetos flexible y reutilizable, es decir, objetos que son más fáciles de implementar. cambiar, probar y reutilizar.
Usar el patrón de diseño de comandos puede resolver estos problemas:
- Debería evitarse la presentación de una solicitud en particular. Es decir, se deben evitar solicitudes de mala educación.
- Debe ser posible configurar un objeto (que invoca una solicitud) con una solicitud.
Implementar (conectar) una solicitud directamente en una clase es inflexible porque acopla la clase a una solicitud particular en tiempo de compilación, lo que hace que sea imposible especificar una solicitud en tiempo de ejecución.
Usar el patrón de diseño de comando describe la siguiente solución:
- Defina objetos separados (command) que encapsulen una solicitud.
- Una clase delega una solicitud a un objeto de comando en lugar de implementar una solicitud en particular directamente.
Esto permite configurar una clase con un objeto de comando que se utiliza para realizar una solicitud. La clase ya no está acoplada a una solicitud particular y no tiene conocimiento (es independiente) de cómo se lleva a cabo la solicitud.
Vea también la clase UML y el diagrama de secuencia a continuación.
Estructura
Clase UML y diagrama de secuencia
En el diagrama de clases UML anterior, la clase Invoker
no implementa una solicitud directamente.
En cambio, Invoker
se refiere a la interfaz Command
para realizar una solicitud (command.execute()
), lo que hace que el Invoker
independientemente de cómo se realice la solicitud.
La clase Command1
implementa la interfaz Command
realizando una acción en un receptor (receiver1.action1()
).
El diagrama de secuencia UML
muestra las interacciones en tiempo de ejecución: el objeto Invoker
llama a execute()
en un objeto Command1
.
Command1
llama a action1()
en un objeto Receiver1
,
que realiza la solicitud.
Diagrama de clases UML
Usos
- Botones GUI y elementos de menú
- En Swing y Borland Delphi programación, un
Action
es un objeto de comando. Además de la capacidad de realizar el comando deseado, un Medida puede tener un icono asociado, atajo de teclado, texto de tooltip, etc. Un botón de barra de herramientas o componente de menú puede ser completamente inicializado utilizando sólo el Medida objeto. - Grabación de macros
- Si todas las acciones del usuario están representadas por objetos de comando, un programa puede registrar una secuencia de acciones simplemente manteniendo una lista de los objetos de comando como se ejecutan. Puede entonces "jugar de nuevo" las mismas acciones ejecutando los mismos objetos de comando de nuevo en secuencia. Si el programa incrusta un motor de scripting, cada objeto de comando puede implementar un toScript() método, y las acciones del usuario se pueden grabar fácilmente como scripts.
- Código móvil
- Utilizando lenguajes como Java, donde el código se puede transmitir/slurped de una ubicación a otra vía URLClassloaders y Codebases, los comandos pueden permitir que nuevos comportamientos sean entregados a lugares remotos (Comando EJB, Maestro Trabajador).
- Deshacer multinivel
- Si todas las acciones de usuario en un programa se implementan como objetos de comando, el programa puede mantener una pila de los comandos más recientemente ejecutados. Cuando el usuario quiere deshacer un comando, el programa simplemente aparece el objeto de comando más reciente y ejecuta su undo() método.
- Redes
- Es posible enviar objetos de comando enteros a través de la red para ser ejecutados en las otras máquinas, por ejemplo acciones de jugador en juegos de computadora.
- Tratamiento paralelo
- Cuando los comandos son escritos como tareas a un recurso compartido y ejecutados por muchos hilos en paralelo (posiblemente en máquinas remotas; esta variante se conoce a menudo como el patrón maestro/trabajador)
- Barras de progreso
- Supongamos que un programa tiene una secuencia de comandos que ejecuta en orden. Si cada objeto de comando tiene un getEstimatedDuration() método, el programa puede calcular fácilmente la duración total. Puede mostrar una barra de progreso que refleja significativamente cuán cerca está el programa para completar todas las tareas.
- Piscinas de pan
- Una típica clase de piscina de hilos de uso general podría tener un público addTask() método que añade un artículo de trabajo a una cola interna de tareas que espera ser realizada. Mantiene una piscina de hilos que ejecutan comandos de la cola. Los elementos en la cola son objetos de comando. Típicamente estos objetos implementan una interfaz común como java.lang.Runnable que permite que el grupo de hilos ejecute el comando a pesar de que el grupo de hilos en sí fue escrito sin ningún conocimiento de las tareas específicas para las cuales sería utilizado.
- Comportamiento trasaccional
- Similar al deshacer, un motor de base de datos o un instalador de software puede mantener una lista de operaciones que han sido o se realizarán. Si uno de ellos falla, todos los demás pueden ser revertidos o descartados (generalmente llamado Rollback). Por ejemplo, si se deben actualizar dos tablas de bases de datos que se refieren entre sí, y la segunda actualización falla, la transacción se puede revertir, de modo que la primera tabla no contenga ahora una referencia inválida.
- Magos
- A menudo un mago presenta varias páginas de configuración para una sola acción que sólo sucede cuando el usuario hace clic en el botón "Finish" en la última página. En estos casos, una manera natural de separar el código de interfaz de usuario del código de aplicación es implementar el asistente utilizando un objeto de comando. El objeto de comando se crea cuando el mago se muestra por primera vez. Cada página wizard almacena sus cambios GUI en el objeto de comando, por lo que el objeto se pobla a medida que el usuario progresa. "Finish" simplemente activa una llamada ejecuta(). De esta manera, la clase de comandos funcionará.
Terminología
La terminología utilizada para describir las implementaciones de patrones de comandos no es coherente y, por lo tanto, puede resultar confusa. Este es el resultado de la ambigüedad, el uso de sinónimos y las implementaciones que pueden oscurecer el patrón original al ir mucho más allá.
- Ambigüedad.
- El término comando es ambiguo. Por ejemplo, Muévete, muévete. puede referirse a un único comando (move up) que debe ejecutarse dos veces, o puede referirse a dos comandos, cada uno de los cuales hace lo mismo (move up). Si el comando anterior se añade dos veces a una pila de deshacer, ambos elementos en la pila se refieren a la misma instancia de comando. Esto puede ser apropiado cuando un comando siempre se puede deshacer de la misma manera (por ejemplo, bajar). Tanto la pandilla de cuatro y el ejemplo Java a continuación utilizan esta interpretación del término comando. Por otro lado, si los últimos comandos se añaden a una pila de deshacer, la pila se refiere a dos objetos separados. Esto puede ser apropiado cuando cada objeto en la pila debe contener información que permite que el comando sea deshecho. Por ejemplo, para deshacer un Eliminar la selección comando, el objeto puede contener una copia del texto borrado para que pueda ser re-insertado, si el Eliminar la selección El comando debe ser deshecho. Tenga en cuenta que el uso de un objeto separado para cada invocación de un comando es también un ejemplo de la cadena de patrón de responsabilidad.
- El término ejecutar también es ambiguo. Puede referirse a ejecutar el código identificado por el objeto de comando ejecutar método. Sin embargo, en la Fundación Presentación de Microsoft se considera que se ha ejecutado un comando cuando el comando ejecutar método ha sido invocado, pero eso no significa necesariamente que el código de aplicación ha funcionado. Eso ocurre sólo después de un nuevo procesamiento de eventos.
- Sinónimos y homónimos.
- Cliente, Fuente, Invoker: el botón, el botón de barra de herramientas o el elemento del menú pulsado, la tecla de acceso directo presionada por el usuario.
- Objeto del Comando, Objeto del Comando, Objeto de Acción: un objeto singleton (por ejemplo, sólo hay un objeto CopyCommand), que sabe acerca de teclas de acceso directo, imágenes de botones, texto de comando, etc. relacionado con el comando. Un objeto fuente/invocador llama al método de ejecución/rendimiento del objeto Comando/Acción. El objeto Comando/Acción notifica los objetos fuente/invocadores apropiados cuando la disponibilidad de un comando/acción ha cambiado. Esto permite que los botones y los elementos del menú se inactiven (grayed out) cuando un comando/acción no se puede ejecutar/performar.
- Receptor, objeto objetivo: el objeto que está a punto de ser copiado, pegado, movido, etc. El objeto receptor posee el método que se llama por el comando ejecutar método. El receptor es normalmente también el objeto objetivo. Por ejemplo, si el objeto receptor es un cursor y el método se llama Mover, entonces uno esperaría que el cursor es el objetivo de la acción MoveUp. Por otro lado, si el código es definido por el objeto de comando en sí mismo, el objeto objetivo será un objeto completamente diferente.
- Comando Objeto, argumentos enrutados de eventos, objeto de evento: el objeto que pasa de la fuente al objeto Comando/Acción, al objeto Target al código que hace el trabajo. Cada pulsador de botón o tecla de acceso directo resulta en un nuevo objeto de comando/evento. Algunas implementaciones agregan más información al objeto comando/event ya que se está pasando de un objeto (por ejemplo, CopyCommand) a otro (por ejemplo, sección del documento). Otras implementaciones ponen objetos de comando/evento en otros objetos de eventos (como una caja dentro de una caja más grande) a medida que se mueven a lo largo de la línea, para evitar conflictos de nombres. (Véase también patrón de cadena de responsabilidad).
- Handler, ExecutedRouted EventHandler, método, función: el código real que hace la copia, pegado, mudanza, etc. En algunas implementaciones el código del manejador es parte del objeto de comando/acción. En otras implementaciones el código forma parte del Objeto Receptor/Target, y en otras implementaciones el código del manejador se mantiene separado de los otros objetos.
- Manager, Undo Manager, Scheduler, Queue, Dispatcher, Invoker: un objeto que pone objetos de comando/evento en una pila de deshacer o redo, o que se aferra a objetos de comando/evento hasta que otros objetos estén listos para actuar en ellos, o que encargue el comando/evento objetos al receptor/objeto o código de manipulador apropiado.
- Implementaciones que van más allá del patrón de comando original.
- Microsoft's Windows Presentation Foundation (WPF), introduce comandos enrutados, que combinan el patrón de comando con el procesamiento de eventos. Como resultado, el objeto de comando ya no contiene una referencia al objeto objetivo ni una referencia al código de aplicación. En su lugar, invocando el objeto de comando ejecutar comando results in a so-called Evento en ruta ejecutado que durante el túnel del evento o el bubbling puede encontrar un llamado vinculante objeto que identifica el objetivo y el código de aplicación, que se ejecuta en ese punto.
Ejemplos
Esta implementación de C++14 se basa en la implementación anterior a C++98 del libro.
#include ■iostream#include - No.clase Comando {}público: // declara una interfaz para ejecutar una operación. virtual vacío ejecutar() = 0; virtual ~Comando() = por defecto;protegida: Comando() = por defecto;};plantilla .nombre Receptor■clase SimpleCommand : público Comando {} // ConcreteCommandpúblico: Tipodef vacío ()Receptor::* Medida)(); // define una unión entre un objeto receptor y una acción. SimpleCommand()std::shared_ptr.Receptor■ receiver_, Medida action_) : receptor()receiver_.#()), acción()action_) {} } SimpleCommand()const SimpleCommand") = Borrador; // regla de tres const SimpleCommand" operador=()const SimpleCommand") = Borrador; // implementos ejecutados invocando la operación(s) correspondiente en el receptor. virtual vacío ejecutar() {} ()receptor- No.acción)(); }privado: Receptor* receptor; Medida acción;};clase MyClass {} // Receptorpúblico: // sabe cómo realizar las operaciones asociadas con realizar una solicitud. Cualquier clase puede servir como receptor. vacío acción() {} std::Cout .. "MyClass:actionn"; }};int principal() {} // Los punteros inteligentes evitan las fugas de memoria. std::shared_ptr.MyClass■ receptor = std::make_shared.MyClass■(); //... std::único_ptr.Comando■ comando = std::make_unique.SimpleCommand.MyClass■ ■()receptor, "MyClass::acción); //... comando-ejecutar();}
La salida del programa es
MyClass::acción
Considere un "simple" cambiar. En este ejemplo configuramos el Switch con dos comandos: encender la luz y apagar la luz.
Un beneficio de esta implementación particular del patrón de comando es que el interruptor se puede usar con cualquier dispositivo, no solo con una luz. Switch en la siguiente implementación de C# enciende y apaga una luz, pero el constructor de Switch puede aceptar cualquier subclase de Command para sus dos parámetros. Por ejemplo, podría configurar el interruptor para arrancar un motor.
utilizando Sistema;namespace CommandPattern;público interfaz ICommand{} vacío Ejecutar();}/* La clase Invoker */público clase Interruptor{} ICommand _closedCommand; ICommand _openedCommand; público Interruptor()ICommand cerradoCommand, ICommand openCommand) {} _closedCommand = cerradoCommand; _openedCommand = openCommand; } // Cerrar el circuito / potencia en público vacío Cerca() {} _closedCommand.Ejecutar(); } // Abrir el circuito / apagar la energía público vacío Abierto() {} _openedCommand.Ejecutar(); }}/* Una interfaz que define acciones que el receptor puede realizar */público interfaz ISwitchable{} vacío PowerOn(); vacío PowerOff();}/* La clase receptora */público clase Luz : ISwitchable{} público vacío PowerOn() {} Consola.WriteLine()"La luz está encendida"); } público vacío PowerOff() {} Consola.WriteLine()"La luz está apagada"); }}/* El Comando para apagar el dispositivo - ConcreteCommand #1 */público clase CloseSwitchCommand : ICommand{} privado ISwitchable _Switchable; público CloseSwitchCommand()ISwitchable conmutable) {} _Switchable = conmutable; } público vacío Ejecutar() {} _Switchable.PowerOff(); }}/* El Comando para encender el dispositivo - ConcreteCommand #2 */público clase OpenSwitchCommand : ICommand{} privado ISwitchable _Switchable; público OpenSwitchCommand()ISwitchable conmutable) {} _Switchable = conmutable; } público vacío Ejecutar() {} _Switchable.PowerOn(); }}/* La clase de prueba o cliente */interna clase Programa{} público estática vacío Main()cuerda[] argumentaciones) {} cuerda argumento = argumentaciones.Duración ■ 0 ? argumentaciones[0].ToUpper() : nulo; ISwitchable lámpara = nuevo Luz(); // Pase referencia a la instancia de la lámpara a cada comando ICommand switchClose = nuevo CloseSwitchCommand()lámpara); ICommand interruptor Abierto = nuevo OpenSwitchCommand()lámpara); // Pase referencia a las instancias de los objetos del comando al interruptor Interruptor @switch = nuevo Interruptor()switchClose, interruptor Abierto); si ()argumento == "ON") {} // Switch (el Invoker) invocará Execute() en el objeto de comando. @switch.Abierto(); } más si ()argumento == "OFF") {} // Switch (el Invoker) invocará el Execute() en el objeto de comando. @switch.Cerca(); } más {} Consola.WriteLine()"Argumento "ON" o "OFF" es requerido."); } }}
Historia
La primera mención publicada sobre el uso de una clase Command para implementar sistemas interactivos parece ser un artículo de 1985 de Henry Lieberman. La primera descripción publicada de un mecanismo de deshacer-rehacer (múltiples niveles), usando una clase Command con métodos ejecutar y deshacer, y una lista de historial, parece ser la primera (1988) edición del libro Construcción de software orientada a objetos de Bertrand Meyer, sección 12.2.
Contenido relacionado
Manzana lisa
Gestión de la movilidad inalámbrica
Limpieza de 8 bits