Patrón de comando - Command pattern

En la programación orientada a objetos , el patrón de comando es un comportamiento patrón de diseño en el que se utiliza un objeto para encapsular toda la información necesaria para llevar a cabo una acción o activar 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 para 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 de comando sabe sobre el 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 entonces hace el trabajo cuando se llama al execute()método en comando . Un objeto invocador sabe cómo ejecutar un comando y, opcionalmente, lleva la contabilidad sobre la ejecución del comando. El invocador no sabe nada sobre un comando concreto, solo conoce la interfaz del comando . El objeto (s) de invocador, los objetos de comando y los objetos de receptor son retenidos por un objeto de cliente , el cliente decide qué objetos de receptor 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 de comando al objeto de 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 de su elección sin la necesidad de conocer la clase del método o los parámetros del método. El uso de un objeto invocador permite realizar cómodamente la contabilidad de las ejecuciones de comandos, así como implementar diferentes modos de comandos, que son administrados por el objeto invocador, sin necesidad de que el cliente sea consciente de la existencia de contabilidad o modos.

Las ideas centrales de este patrón de diseño reflejan de cerca 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 de comando es un argumento de primera clase.

Descripción general

El patrón de diseño de comandos es uno de los veintitrés patrones de diseño GoF bien 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.

El uso del patrón de diseño de comandos puede resolver estos problemas:

  • Debe evitarse vincular el invocador de una solicitud a una solicitud en particular. Es decir, se deben evitar las solicitudes cableadas.
  • Debería ser posible configurar un objeto (que invoca una solicitud) con una solicitud.

La implementación (cableado fijo) de 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.

El uso del patrón de diseño de comandos describe la siguiente solución:

  • Defina objetos (comando) separados 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 en particular y no tiene conocimiento (es independiente) de cómo se lleva a cabo la solicitud.

Consulte también el diagrama de secuencia y clases de UML a continuación.

Estructura

Diagrama de secuencia y clase UML

Un ejemplo de diagrama de secuencia y clase UML para el patrón de diseño de Command.

En el diagrama de clases de UML anterior , la Invokerclase no implementa una solicitud directamente. En cambio, se Invokerrefiere a la Commandinterfaz para realizar una solicitud ( command.execute()), lo que hace que sea Invokerindependiente de cómo se realiza la solicitud. La Command1clase implementa la Commandinterfaz realizando una acción en un receptor ( receiver1.action1()).

El diagrama de secuencia UML muestra las interacciones en tiempo de ejecución: El Invokerobjeto llama execute()a un Command1objeto. Command1llama action1()a un Receiver1objeto, que realiza la solicitud.

Diagrama de clases UML

Diagrama UML del patrón de comando

Usos

Elementos de menú y botones de la GUI
En la programación de Swing y Borland Delphi , an Actiones un objeto de comando. Además de la capacidad de ejecutar el comando deseado, una acción puede tener un icono asociado, un método abreviado de teclado, texto de información sobre herramientas, etc. Un botón de la barra de herramientas o un componente de un elemento de menú se puede inicializar completamente usando solo el objeto Acción .
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 a medida que se ejecutan. A continuación, puede "reproducir" las mismas acciones ejecutando los mismos objetos de comando nuevamente en secuencia. Si el programa incorpora un motor de secuencias de comandos, cada objeto de comando puede implementar un método toScript () y las acciones del usuario se pueden registrar fácilmente como secuencias de comandos.
Código móvil
Al usar lenguajes como Java, donde el código se puede transmitir / sorber de una ubicación a otra a través de URLClassloaders y Codebases, los comandos pueden permitir que se entregue un nuevo comportamiento a ubicaciones remotas (EJB Command, Master Worker).
Deshacer multinivel
Si todas las acciones del usuario en un programa se implementan como objetos de comando, el programa puede mantener una pila de los comandos ejecutados más recientemente. Cuando el usuario quiere deshacer un comando, el programa simplemente muestra el objeto de comando más reciente y ejecuta su método undo () .
Redes
Es posible enviar objetos de comando completos a través de la red para que se ejecuten en otras máquinas, por ejemplo, acciones del jugador en juegos de computadora.
Procesamiento en paralelo
Donde los comandos se escriben como tareas en un recurso compartido y se ejecutan por muchos subprocesos en paralelo (posiblemente en máquinas remotas; esta variante a menudo se conoce como el patrón Maestro / Trabajador)
Barras de progreso
Suponga que un programa tiene una secuencia de comandos que ejecuta en orden. Si cada objeto de comando tiene un método getEstimatedDuration () , el programa puede estimar fácilmente la duración total. Puede mostrar una barra de progreso que refleje de manera significativa qué tan cerca está el programa de completar todas las tareas.
Grupos de subprocesos
Una clase de grupo de subprocesos típica de uso general puede tener un método addTask () público que agrega un elemento de trabajo a una cola interna de tareas que esperan ser realizadas. Mantiene un grupo de subprocesos que ejecutan comandos desde la cola. Los elementos de la cola son objetos de comando. Por lo general, estos objetos implementan una interfaz común como java.lang.Runnable que permite que el grupo de subprocesos ejecute el comando aunque la clase del grupo de subprocesos en sí se escribió sin ningún conocimiento de las tareas específicas para las que se usaría.
Comportamiento transaccional
De manera similar a deshacer, un motor de base de datos o un instalador de software puede mantener una lista de operaciones que se han realizado o se realizarán. Si uno de ellos falla, todos los demás pueden revertirse o descartarse (generalmente se denomina reversión ). Por ejemplo, si dos tablas de base de datos que se refieren entre sí deben actualizarse y la segunda actualización falla, la transacción se puede revertir, de modo que la primera tabla ahora no contenga una referencia no válida.
Magos
A menudo, un asistente presenta varias páginas de configuración para una sola acción que ocurre solo cuando el usuario hace clic en el botón "Finalizar" en la última página. En estos casos, una forma natural de separar el código de la interfaz de usuario del código de la aplicación es implementar el asistente mediante un objeto de comando. El objeto de comando se crea cuando se muestra el asistente por primera vez. Cada página del asistente almacena sus cambios en la GUI en el objeto de comando, por lo que el objeto se completa a medida que avanza el usuario. "Finish" simplemente activa una llamada a execute () . De esta forma, la clase de comando funcionará.

Terminología

La terminología utilizada para describir las implementaciones de patrones de comando 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 yendo mucho más allá.

  1. Ambigüedad.
    1. El término comando es ambiguo. Por ejemplo, mover hacia arriba, mover hacia arriba puede referirse a un solo comando (mover hacia arriba) que debe ejecutarse dos veces, o puede referirse a dos comandos, cada uno de los cuales hace lo mismo (mover hacia arriba). Si el comando anterior se agrega dos veces a una pila de deshacer, ambos elementos de 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, mover hacia abajo). Tanto el ejemplo de Gang of Four como el de Java a continuación utilizan esta interpretación del término comando . Por otro lado, si los últimos comandos se agregan a una pila de deshacer, la pila se refiere a dos objetos separados. Esto puede ser apropiado cuando cada objeto de la pila debe contener información que permita deshacer el comando. Por ejemplo, para deshacer un comando de eliminación de selección , el objeto puede contener una copia del texto eliminado para que se pueda volver a insertar, si el comando de eliminación de selección debe deshacerse. Tenga en cuenta que el uso de un objeto separado para cada invocación de un comando también es un ejemplo del patrón de cadena de responsabilidad .
    2. El término ejecutar también es ambiguo. Puede referirse a ejecutar el código identificado por el método de ejecución del objeto de comando . Sin embargo, en Windows Presentation Foundation de Microsoft se considera que se ha ejecutado un comando cuando se ha invocado el método de ejecución del comando , pero eso no significa necesariamente que se haya ejecutado el código de la aplicación. Eso ocurre solo después de algún procesamiento adicional de eventos.
  2. Sinónimos y homónimos .
    1. Cliente, fuente, invocador : el botón, botón de la barra de herramientas o elemento de menú en el que se hace clic, la tecla de acceso directo presionada por el usuario.
    2. Objeto de comando, Objeto de comando enrutado, Objeto de acción : un objeto singleton (por ejemplo, solo hay un objeto CopyCommand), que conoce las teclas de método abreviado, imágenes de botones, texto de comando, etc. relacionados con el comando. Un objeto fuente / invocador llama al método execute / performAction del objeto Command / Action. El objeto Comando / Acción notifica a los objetos fuente / invocador apropiados cuando la disponibilidad de un comando / acción ha cambiado. Esto permite que los botones y elementos del menú se vuelvan inactivos (atenuados) cuando un comando / acción no se puede ejecutar / realizar.
    3. Receptor, Objeto de destino : el objeto que está a punto de ser copiado, pegado, movido, etc. El objeto receptor es dueño del método que es llamado por el método de ejecución del comando . El receptor suele ser también el objeto de destino. Por ejemplo, si el objeto receptor es un cursor y el método se llama moveUp , entonces uno esperaría que el cursor sea el objetivo de la acción moveUp. Por otro lado, si el código está definido por el propio objeto de comando, el objeto de destino será un objeto completamente diferente.
    4. Objeto de comando, argumentos de evento enrutados, objeto de evento : el objeto que se pasa de la fuente al objeto de comando / acción, al objeto de destino al código que hace el trabajo. Cada clic de botón o tecla de método abreviado da como resultado un nuevo comando / objeto de evento. Algunas implementaciones agregan más información al objeto de comando / evento a medida que se pasa de un objeto (por ejemplo, CopyCommand) a otro (por ejemplo, sección de documento). Otras implementaciones colocan objetos de comando / evento en otros objetos de evento (como un cuadro dentro de un cuadro más grande) a medida que se mueven a lo largo de la línea, para evitar conflictos de nombres. (Ver también patrón de cadena de responsabilidad ).
    5. Handler, ExecutedRoutedEventHandler, método, función : el código real que copia, pega, mueve, etc. En algunas implementaciones, el código del controlador es parte del objeto de comando / acción. En otras implementaciones, el código es parte del Receptor / Objeto de destino, y en otras implementaciones, el código del controlador se mantiene separado de los otros objetos.
    6. Administrador de comandos, Administrador de deshacer, Programador, Cola, Distribuidor, Invocador : un objeto que coloca los objetos de comando / evento en una pila de deshacer o rehacer, o que se aferra a los objetos de comando / evento hasta que otros objetos estén listos para actuar sobre ellos, o que enruta los objetos de comando / evento al receptor / objeto de destino apropiado o al código del controlador.
  3. Implementaciones que van mucho más allá del patrón de comando original.
    1. Windows Presentation Foundation (WPF) de Microsoft presenta 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 de destino ni una referencia al código de la aplicación. En cambio, invocar el comando de ejecución del objeto de comando da como resultado un llamado Evento Enrutado Ejecutado que durante el túnel o el burbujeo del evento puede encontrar un llamado objeto de enlace que identifica el objetivo y el código de la aplicación, que se ejecuta en ese punto.

Ejemplo

Considere un cambio "simple". En este ejemplo configuramos el Switch con dos comandos: encender la luz y apagar la luz.

Una ventaja 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. El Switch en la siguiente implementación de C # enciende y apaga una luz, pero el constructor del Switch es capaz de aceptar cualquier subclase de Command para sus dos parámetros. Por ejemplo, puede configurar el Switch para que arranque un motor.

using System;

namespace CommandPattern
{    
    public interface ICommand
    {
        void Execute();
    }

    /* The Invoker class */
    public class Switch
    {
        ICommand _closedCommand;
        ICommand _openedCommand;

        public Switch(ICommand closedCommand, ICommand openedCommand)
        {
            this._closedCommand = closedCommand;
            this._openedCommand = openedCommand;
        }

        // Close the circuit / power on
        public void Close()
        {
           this._closedCommand.Execute();
        }

        // Open the circuit / power off
        public void Open()
        {
            this._openedCommand.Execute();
        }
    }

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

    /* The Receiver class */
    public class Light : ISwitchable
    {
        public void PowerOn()
        {
            Console.WriteLine("The light is on");
        }

        public void PowerOff()
        {
            Console.WriteLine("The light is off");
        }
    }

    /* The Command for turning on the device - ConcreteCommand #1 */
    public class CloseSwitchCommand : ICommand
    {
        private ISwitchable _switchable;

        public CloseSwitchCommand(ISwitchable switchable)
        {
            _switchable = switchable;
        }

        public void Execute()
        {
            _switchable.PowerOn();
        }
    }

    /* The Command for turning off the device - ConcreteCommand #2 */
    public class OpenSwitchCommand : ICommand
    {
        private ISwitchable _switchable;

        public OpenSwitchCommand(ISwitchable switchable)
        {
            _switchable = switchable;
        }

        public void Execute()
        {
            _switchable.PowerOff();
        }
    }

    /* The test class or client */
    internal class Program
    {
        public static void Main(string[] arguments)
        {
            string argument = arguments.Length > 0 ? arguments[0].ToUpper() : null;

            ISwitchable lamp = new Light();

            // Pass reference to the lamp instance to each command
            ICommand switchClose = new CloseSwitchCommand(lamp);
            ICommand switchOpen = new OpenSwitchCommand(lamp);

            // Pass reference to instances of the Command objects to the switch
            Switch @switch = new Switch(switchClose, switchOpen);

            if (argument == "ON")
            {
                // Switch (the Invoker) will invoke Execute() on the command object.
                @switch.Close();
            }
            else if (argument == "OFF")
            {
                // Switch (the Invoker) will invoke the Execute() on the command object.
                @switch.Open();
            }
            else
            {
                Console.WriteLine("Argument \"ON\" or \"OFF\" is required.");
            }
        }
    }
}


Ver también

Fuentes

La primera mención publicada del 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 (de varios niveles), utilizando una clase Command con métodos de ejecución y deshacer , y una lista de historial, parece ser la primera edición (1988) del libro de Bertrand Meyer , software orientado a objetos. Construcción , sección 12.2.

Referencias

enlaces externos