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 .
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
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 ; } }
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 ()
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 );
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)
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