Comando (patrón de diseño)

La versión actual de la página aún no ha sido revisada por colaboradores experimentados y puede diferir significativamente de la versión revisada el 20 de septiembre de 2019; las comprobaciones requieren 8 ediciones .
Equipo
dominio
Tipo de conductual
Objetivo para procesar el comando como un objeto
Plantillas relacionadas Enlazador , Guardián , Prototipo , Solitario
Descrito en Patrones de diseño

Un comando es un patrón de diseño de comportamiento  utilizado en la programación orientada a objetos que representa una acción. El objeto de comando contiene la acción en sí y sus parámetros.

Propósito

Cree una estructura en la que la clase del remitente y la clase del receptor no dependan directamente entre sí. Organizar una devolución de llamada a una clase que incluye la clase del remitente.

Descripción

En la programación orientada a objetos, el patrón de diseño de comandos es un patrón de comportamiento en el que se utiliza un objeto para encapsular toda la información necesaria para realizar una acción o generar 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.

Siempre se asocian cuatro términos con el patrón de comando: comandos (comando), receptor de comando (receptor), llamador de comando (invocador) y cliente (cliente). El objeto Command conoce al receptor e invoca el método del receptor. Los valores de los parámetros del receptor se almacenan en el comando. La persona que llama (invocador) sabe cómo ejecutar el comando y posiblemente realiza un seguimiento de los comandos ejecutados. La persona que llama (invocador) no sabe nada sobre un comando en particular, solo sabe sobre la interfaz. Ambos objetos (el objeto de llamada y varios objetos de comando) pertenecen al objeto de cliente. El cliente decide qué comandos ejecutar y cuándo. Para ejecutar un comando, pasa el objeto de comando a la persona que llama (invocador).

El uso de objetos de comando facilita la creación de componentes compartidos que necesita delegar o realizar llamadas a métodos en cualquier momento sin tener que conocer los métodos de clase o los parámetros de métodos. El uso del objeto llamador (invoker) le permite mantener un registro de los comandos ejecutados sin necesidad de que el cliente conozca este modelo de contabilidad (dicha contabilidad puede ser útil, por ejemplo, para implementar el comando deshacer y rehacer).

Aplicación

El patrón Comando puede ser útil en los siguientes casos.

Botones de la interfaz de usuario y elementos de menú

En Swing y Borland Delphi , una Acción es un objeto de comando. Además de poder ejecutar el comando deseado, una Acción puede tener un icono asociado, un atajo de teclado, texto de información sobre herramientas, etc. Un botón de la barra de herramientas o un elemento de menú se puede inicializar por completo usando solo un objeto de acción .

Grabación de macros

Si todas las acciones del usuario se representan como objetos de comando, el programa puede registrar una secuencia de acciones simplemente almacenando una lista de objetos de comando en el orden en que se ejecutan. Luego puede "reproducir" las mismas acciones ejecutando los mismos objetos de comando en la misma secuencia.

Operaciones de deshacer multinivel ( Deshacer )

Si todas las acciones del usuario en el programa se implementan como objetos de comando, el programa puede guardar una pila de los últimos comandos ejecutados. Cuando el usuario quiere cancelar un comando, el programa simplemente saca el último objeto de comando y ejecuta su método undo() .

redes

Puede enviar objetos de comando a través de la red para que se ejecuten en otra máquina, como la acción de un jugador en un juego de computadora.

Barras de progreso

Supongamos 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 del proceso. Puede mostrar una barra de progreso que refleja qué tan cerca está el programa de completar todas las tareas.

Grupos de subprocesos

Una clase típica de grupo de subprocesos de propósito general podría tener un método addTask() que agrega un elemento de trabajo a una cola interna de tareas pendientes. Mantiene un grupo de subprocesos que ejecutan comandos desde una cola. Los elementos en 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 comandos incluso si se escribió sin ningún conocimiento de las tareas específicas para las que se utilizará.

Actas

Similar a la operación "deshacer" , un sistema de gestión de base de datos (DBMS) o un instalador de software puede almacenar una lista de operaciones que se han realizado o se realizarán. Si uno de ellos falla, todos los demás pueden cancelarse o descartarse (comúnmente llamado reversión). Por ejemplo, si es necesario actualizar dos tablas de bases de datos relacionadas y falla la segunda actualización, el sistema puede revertir la transacción para que la primera tabla no contenga un enlace no válido.

Maestros

A menudo , un asistente (asistente de configuración o lo que sea) presenta múltiples páginas de configuración para una sola acción que solo ocurre cuando el usuario hace clic en el botón "Finalizar" en la última página. En estos casos, la forma natural de separar el código de la interfaz de usuario del código de la aplicación es implementar el asistente con un objeto de comando. El objeto de comando se crea la primera vez que se muestra el asistente. Cada página del asistente guarda sus cambios en el objeto de comando, por lo que el objeto se completa a medida que el usuario navega. El botón "Listo" simplemente activa el método de ejecución () para ejecutar.

Ejemplos

Ejemplo de C++

Texto fuente en C++ # include < iostream > # include < vector > # include < cadena > usando el espacio de nombres std ; clase Documento { vector < cadena > datos ; público : Documento () { datos . reserva ( 100 ); // al menos por 100 líneas } void Insertar ( int line , const string & str ) { if ( line <= data . size () ) data . insertar ( datos . comenzar () + línea , str ); else cout << "¡Error!" << endl ; } void Remove ( int line ) { if ( !( line > data . size () ) ) datos . borrar ( datos.begin ( ) + línea ) ; else cout << "¡Error!" << endl ; } cadena y operador [] ( int x ) { datos de retorno [ x ]; } void Mostrar () { for ( int i = 0 ; i < datos . tamaño (); ++ i ) { cout << i + 1 << ". " << datos [ i ] << endl ; } } }; clase Comando { protegido : Documento * doc ; public : virtual ~ Comando () {} virtual void Ejecutar () = 0 ; vacío virtual unExecute () = 0 ; void setDocument ( Documento * _doc ) { doc = _doc ; } }; class InsertCommand : public Command { int line ; cadena cadena ; public : InsertCommand ( int _line , const string & _str ): line ( _line ), str ( _str ) {} void Ejecutar () { doc -> Insertar ( línea , cadena ); } void unExecute () { doc -> Eliminar ( línea ); } }; class Invoker { vector < Command *> DoneCommands ; documento doc ; Comando * comando ; public : void Insertar ( int line , string str ) { command = new InsertCommand ( line , str ); comando -> establecerDocumento ( & doc ); comando -> Ejecutar (); ComandosHechos . push_back ( comando ); } void Deshacer () { if ( DoneCommands . size () == 0 ) { cout << "¡No hay nada que deshacer!" << endl ; } else { comando = DoneCommands . atrás (); ComandosHechos . pop_back (); comando -> no ejecutado (); // ¡¡¡No olvides borrar el comando!!! borrar comando ; } } vacío Mostrar () { doc . mostrar (); } }; int principal () { char s = '1' ; línea int , línea_b ; cadena cadena ; Invocador inv ; while ( s != 'e' ) { cout << "Qué hacer: \n1.Agregar una línea\n2.Deshacer el último comando" << endl ; cin >> s ; switch ( s ) { case '1' : cout << "Qué línea insertar: " ; cin >> linea ; --línea ; _ cout << "Qué insertar: " ; cin >> cadena ; inversión _ insertar ( línea , cadena ); romper ; caso '2' : inv . Deshacer (); romper ; } cout << "$$$DOCUMENTO$$$" << endl ; inversión _ mostrar (); cout << "$$$DOCUMENTO$$$" << endl ; } }

Ejemplo de Python

Código fuente en Python de abc import ABCMeta , método abstracto clase Tropa : """ Receptor - Tropa objeto """ def move ( self , dirección : str ) -> Ninguno : """ Empezar a moverse en cierta dirección """ print ( 'El escuadrón empezó a moverse {} ' . format ( dirección )) def detener ( auto ) -> Ninguno : """ Detener """ imprimir ( 'Escuadrón detenido' ) Comando de clase ( metaclase = ABCMeta ): """ Clase base para todos los comandos """ @abstractmethod def ejecutar ( self ) -> Ninguno : """ Continuar para ejecutar el comando """ pasar @abstractmethod def no ejecutado ( auto ) -> Ninguno : """ Comando no ejecutado """ pasar class AttackCommand ( Command ): """ El comando para ejecutar el ataque es """ def __init__ ( self , tropa : Tropa ) -> Ninguno : """ Constructor. :param tropa: la tropa a la que se asocia el comando " "" self .tropa = tropa def ejecutar ( auto ) -> Ninguno : auto . tropa _ mover ( 'adelante' ) def no ejecutado ( self ) -> Ninguno : self . tropa _ detener () clase RetreatCommand ( Comando ): """ Retirar comando """ def __init__ ( self , troop : Troop ) -> Ninguno : """ Constructor. :param troop: la tropa con la que está asociado el comando """ self . tropa = tropa def ejecutar ( auto ) -> Ninguno : auto . tropa _ mover ( 'atrás' ) def no ejecutado ( self ) -> Ninguno : self . tropa _ detener () class TroopInterface : """ Invoker: una interfaz a través de la cual puede emitir comandos a un escuadrón específico """ def __init__ ( self , ataque : AttackCommand , retirada : RetreatCommand ) -> Ninguno : """ Constructor. :param ataque: comando de ataque :param retirada: comando de retirada " "" self .attack_command = atacar a uno mismo .retreat_command = retirarse a sí mismo .current_command = Ninguno # comando actualmente en ejecución ataque def ( auto ) -> Ninguno : auto . comando_actual = self . ataque_comando auto . comando_ataque . ejecutar () def retirarse ( auto ) -> Ninguno : auto . comando_actual = self . retirada_comando auto . comando_retirada . ejecutar () def stop ( self ) -> Ninguno : si self . comando_actual : self . comando_actual . no ejecutado () self . comando_actual = Ninguno más : imprimir ( 'La unidad no puede detenerse porque no se mueve' ) if __name__ == '__principal__' : tropa = Tropa () interfaz = TroopInterface ( AttackCommand ( tropa ), RetreatCommand ( tropa )) interfaz . Interfaz de ataque () . parada () interfaz . interfaz de retiro () . detener ()

Ejemplo de PHP5

código fuente PHP5 <?php /** * Clase abstracta "comandos" * @abstract */ clase abstracta Comando { función abstracta pública Ejecutar (); función abstracta pública UnExecute (); } /** * La clase del "comando" concreto */ class CalculatorCommand extends Command { /** * Operación de comando actual * * @var string */ public $operator ; /** * Operando actual * * @var mixed */ public $operando ; /** * La clase para la que es el comando * * @var objeto de la clase Calculadora */ public $calculadora ; /** * Constructor * * @param objeto $calculadora * @param string $operador * @param mixed $operando */ public function __construct ( $calculadora , $operador , $operando ) { $this -> calculadora = $calculadora ; $esto -> operador = $operador ; $esto -> operando = $operando ; } /** * Padre reimplementado::Función Ejecutar() */ función pública Ejecutar () { $esto -> calculadora -> Operación ( $esto -> operador , $esto -> operando ); } /** * Función padre::UnExecute() reimplementada */ función pública UnExecute () { $this -> calculadora -> Operación ( $this -> Undo ( $this -> operator ), $this -> operando ); } /** * ¿Qué acción se debe deshacer? * * @private * @param string $operador * @return string */ función privada Deshacer ( $operador ) { //busca el reverso para cada acción realizada switch ( $operador ) { case '+' : $undo = '-' ; romper ; case '-' : $deshacer = '+' ; romper ; caso '*' : $deshacer = '/' ; romper ; caso '/' : $deshacer = '*' ; romper ; predeterminado : $deshacer = ' ' ; romper ; } devuelve $deshacer ; } } /** * Clase receptora y ejecutora de "comandos" */ class Calculator { /** * Resultado actual de la ejecución del comando * * @private * @var int */ private $curr = 0 ; public function Operation ( $operador , $operando ) { //seleccionar operador para calcular el resultado switch ( $operador ) { case '+' : $this -> curr += $operando ; romper ; case '-' : $this -> curr -= $operando ; romper ; case '*' : $this -> curr *= $operando ; romper ; case '/' : $this -> curr /= $operando ; romper ; } print ( "Resultado actual = $this->curr (después de ejecutar $operador c $operando )" ); } } /** * Clase que llama a los comandos */ class User { /** * Esta clase recibirá los comandos para ser ejecutados * * @private * @var objeto de la clase Calculadora */ private $calculadora ; /** * Array de operaciones * * @private * @var array */ private $commands = array (); /** * Comando actual en la matriz de operaciones * * @private * @var int */ private $current = 0 ; public function __construct () { // crea una instancia de la clase que ejecutará los comandos $this -> calculadora = nueva Calculadora (); } /** * Función para devolver comandos cancelados * * @param int $niveles número de operaciones a devolver */ public function Redo ( $niveles ) { print ( " \n ---- Repetir operaciones de $niveles " ); // Operaciones de retorno para ( $i = 0 ; $i < $niveles ; $i ++ ) if ( $this -> current < count ( $this -> commands ) - 1 ) $this -> commands [ $this - > actual ++ ] -> Ejecutar (); } /** * Comando deshacer función * * @param int $niveles número de operaciones de deshacer */ public function Deshacer ( $niveles ) { print ( " \n ---- Deshacer $niveles operaciones" ); // Deshacer operaciones para ( $i = 0 ; $i < $niveles ; $i ++ ) if ( $this -> actual > 0 ) $this -> commands [ -- $this -> current ] -> UnExecute ( ); } /** * Función de ejecución de comando * * @param string $operador * @param mixed $operando */ public function Calcular ( $operador , $operando ) { // Crear un comando de operación y ejecutarlo $comando = new CalculatorCommand ( $this -> calculadora , $operador , $operando ); $comando -> Ejecutar (); // Agrega una operación a la matriz de operaciones e incrementa el contador de la operación actual $this -> commands [] = $command ; $este -> actual ++ ; } } $usuario = nuevo Usuario (); // Comandos arbitrarios $usuario -> Calcular ( '+' , 100 ); $usuario -> Calcular ( '-' , 50 ); $usuario -> Calcular ( '*' , 10 ); $usuario -> Calcular ( '/' , 2 ); // Deshacer 4 comandos $usuario -> Deshacer ( 4 ); // Devuelve 3 comandos cancelados. $usuario -> Rehacer ( 3 );

Ejemplo de Java

Fuente Java

Para implementar la correspondencia de los nombres de las operaciones con la acción, las operaciones sobre la lámpara (encender, apagar) se trasladan a una instancia de clases SwitchOnCommandy SwitchOffCommandambas clases implementan la interfaz Command.

importar java.util.HashMap ; /** La interfaz de Comando */ interfaz Comando { void ejecutar (); } /** La clase Invoker */ class Switch { private final HashMap < String , Command > commandMap = new HashMap <> (); registro de vacío público ( String commandName , Command command ) { commandMap . poner ( nombreComando , comando ); } public void ejecutar ( String commandName ) { Command command = commandMap . obtener ( nombreComando ); if ( command == null ) { throw new IllegalStateException ( " no se registró ningún comando para " + commandName ); } comando . ejecutar (); } } /** La clase del Receptor */ class Light { public void turnOn () { System . fuera _ println ( "La luz esta encendida" ); } turnOff public void () { Sistema . fuera _ println ( "La luz esta apagada" ); } } /** El comando para encender la luz - ConcreteCommand #1 */ class SwitchOnCommand implements Command { private final Light light ; SwitchOnCommand público ( Luz de luz ) { este . luz = luz ; } @Override // Comando public void ejecutar () { light . encender (); } } /** El comando para apagar la luz - ConcreteCommand #2 */ class SwitchOffCommand implements Command { private final Light light ; SwitchOffCommand público ( luz de luz ) { este . luz = luz ; } @Override // Comando public void ejecutar () { light . apagar (); } } public class CommandDemo { public static void main ( final String [] argumentos ) { Light lamp = new Light (); Comando switchOn = new SwitchOnCommand ( lámpara ); Comando switchOff = new SwitchOffCommand ( lámpara ); Switch mySwitch = nuevo Switch (); mi interruptor . registrarse ( "encendido" , encender ); mi interruptor . registro ( "apagado" , apagar ); mi interruptor . ejecutar ( "encendido" ); mi interruptor . ejecutar ( "apagado" ); } } Uso de la interfaz funcional

A partir de Java 8, no es obligatorio crear clases SwitchOnCommandy SwitchOffCommanden su lugar podemos usar un operador ::como se muestra en el siguiente ejemplo

public class CommandDemo { public static void main ( final String [] argumentos ) { Light lamp = new Light (); Comando encender = lámpara :: encender ; Comando apagar = lámpara :: apagar ; Switch mySwitch = nuevo Switch (); mi interruptor . registrarse ( "encendido" , encender ); mi interruptor . registro ( "apagado" , apagar ); mi interruptor . ejecutar ( "encendido" ); mi interruptor . ejecutar ( "apagado" ); } }

Ejemplo de Swift 5

Código fuente en Swift 5 Comando de protocolo { función ejecutar () } // llamador interruptor de clase { enum SwitchAction { caso encendido, apagado } estado de var: ¿Cadena? var acción: ¿Luz? registro func(_comando: Luz) { self.action = comando } func ejecutar (_ nombre de comando: SwitchAction) { if nombreComando == .on { acción?.activar() } else if nombreComando == .off { acción?.apagar() } } } // Receptor luz de clase { func encender() { imprimir("La luz esta ENCENDIDA") } func apagar() { imprimir("La luz esta apagada") } } clase SwitchOnCommand: Comando { luz var privada: Luz init(_luz: Luz) { self.luz = luz } func ejecutar() { luz.encender() } } clase SwitchOffCommand: Comando { luz var privada: Luz init(_luz: Luz) { self.luz = luz } func ejecutar() { luz.apagar() } } // USAR dejar invocador = Switch() dejar receptor = Luz() invocador.registrar(receptor) invocador.execute(.on)

Ejemplo de rubí

código fuente rubí módulo EngineCommands # Clase abstracta 'Comando' clase Comando def ejecutar fin fin # Clase de receptor Motor attr_reader :estado def inicializar rpm @state , @rpm = false , rpm si rpm . ¿es un? Extremo entero def activar ; @estado = verdadero ; fin def apagar ; @estado = falso ; final final # ConcreteCommand1 class CommandTurnOn < Comando def inicializar motor @engine = motor si motor . ¿es un? extremo del motor definitivamente ejecutar @engine . encender final final # ConcreteCommand2 class CommandTurnOff < Comando def inicializar motor @engine = motor si motor . ¿es un? extremo del motor definitivamente ejecutar @engine . apagar fin fin # Invoker class Invoker def initialize @commands = Hash . nuevo fin def registerCommand commandName , command @commands [ commandName ] = command if command . ¿es un? Comando y @comandos [ nombreComando ]. ¿es un? Final de NilClass def executeCommand commandName @command = @commands [ commandName ] a menos que @command . ¿es un? NilClass @comando . ejecutar de lo contrario generar TypeError . nuevo final final final final # Módulo de cliente Cliente incluye EngineCommands invocador = invocador . nuevo motor = motor . nuevo ( 250 ) comandoActivar = ComandoActivar . nuevo ( motor ) commandTurnOff = CommandTurnOff . nuevo ( motor ) invocador _ registerCommand "turnOn" , commandTurnOn invocador . registrarse Comando "apagar" , comando Apagar pone " \t estado del motor antes de usar el comando: #{ motor . estado } " # => Estado del motor antes de usar el comando: falso pone " \t estado del motor después de usar el comando 'encender': #{ invocador . ejecutar el comando "encender" } " # => Estado del motor después de usar el comando 'turnOn': true pone " \t Estado del motor después de usar el comando 'turnOff': #{ invocador . executeCommand "turnOff" } " # => Estado del motor después de usar el comando 'turnOff': fin falso

Enlaces