Comando (padrón de deseño)

Na programación orientada a obxectos, Comando é un padrón de deseño no que un obxecto é usado para representar e encapsular toda a información para chamar un método máis adiante no tempo. Esta información inclúe o nome do método, o obxecto que contén o método e valores para os parámetros do método.

Intención

editar

Este padrón permite solicitar unha operación a un obxecto sen coñecer realmente o contido desta operación, nin o receptor real da mesma. Para facelo posible encapsúlase a petición como un obxecto, facilitando ademais a parametrización dos métodos.

Tres termos sempre asociados co padrón Comando son cliente, invocador e receptor. O cliente instancia o obxecto comando e lle proporciona información necesaria para chamar o método máis tarde. O invocador decide cando se debe chamar o método. O receptor é unha instancia da clase que contén o código do método.

Usando obxectos de comando é máis sinxelo construír compoñentes xerais que necesitan delegar, secuenciar ou executar chamadas a métodos nun momento elixido por eles sen necesidade de coñecer o propietario do método ou os seus parámetros.

Propósito

editar
  • Ofrecer unha interface común que permita invocar as accións de maneira uniforme e estender o sistema con novas accións de forma máis sinxela.
  • Encapsula unha mensaxe como un obxecto, permitindo xestionar rexistros e colas de operacións ademais de desfacelas.
  • Soportar restaurar o estado a partir dun momento dado.

Motivo

editar
  • O concepto de "orde" pode ser ambiguo e complexo nos sistemas actuais e ao mesmo tempo moi estendido: intérpretes de ordes do sistema operativo, linguaxes de macros de paquetes ofimáticos, xestores de bases de datos, protocolos de servidores de Internet etc.
  • Este padrón presenta unha forma sinxela e versátil de implementar un sistema baseado en comandos facilitándose o seu uso e ampliación.

Aplicacións

editar
  • Facilitar a parametrización das accións a realizar.
  • Independizar o momento de petición do de execución.
  • Implementar CallBacks, especificando que ordes queremos que se executen en certas situacións doutras ordes. É dicir, un parámetro dunha orde pode ser outra orde a executar.
  • Dar soporte para "desfacer" as operacións.
  • Desenvolver sistemas empregando ordes de alto nivel que se constrúen con operacións sinxelas (primitivas).

Terminoloxía

editar

A terminoloxía empregada para describir a implementación do padrón comando (command pattern) non é consistente e pode polo tanto ser confusa. Isto resulta da ambigüidade, o uso de sinónimos e implementacións que pode escurecer o padrón orixinal por ir máis aló del.

  1. Ambigüidade.
    1. O termo orde é ambiguo. Por exemplo: mover arriba; mover arriba pode referirse a unha orde simple (mover arriba) que debe executarse dúas veces, ou pode referirse a dúas ordes, en cada unha das cales ocorre a mesma acción (mover arriba). Se a primeira versión da orde é engadida dúas veces nunha pila de retrotracción (undo stack), ambox ítems na pila refírense á mesma instancia da orde. Isto pode ser apropiado cando un comando pode ser sempre retrotraído do mesmo modo (por exemplo: mover abaixo). Tanto a Banda dos catro (Gang of Four) como o Command (padrón de deseño)#Java|exemplo Java de aquí abaixo usan esta interpretación do termo orde. Por outro lado, se a interpretación posterior é o que se engade á pila de retrotracción, a pila refírese a dous obxectos separados. Isto pode ser apropiado cando cada obxecto na pila debe conter información que permita ao comando ser retrotraído (desfeito). Por exemplo, para retrotraer unha orde borrar selección, a orde debe conter unha copia do texto eliminado tal que poida ser reinsertado se a orde de borrar selección debe ser retrotraída (ver Memento (padrón de deseño) | padrón recordo (Memento). Nótese que usar un obxecto separado por cada invocación da orde é tamén un exemplo do Chain of Responsability (padrón de deseño)| padrón cadea de responsabilidades.
    2. O termo executar tamén é ambiguo. Pódese referir á execución do código identificado polo método executar do obxecto orde. Porén en Windows Presentation Foundation de Microsoft unha orde considérase executada cando o método executar da orde foi invocado, pero non significa necesariamente que o código da aplicación fora executado. Iso ocorre só tras algún procesado de eventos máis.
  2. Sinónimos e homónimos.
    1. Cliente (Client), Fonte (Source), Invocador (Invoker): o botón, o botón de barra de ferramentas, o ítem de menú clicado, o atallo de teclado presionado polo usuario.
    2. Obxecto Orde (Command Object), Obxecto Orde Enrutada (Routed Command Object), Obxecto Acción (Action Object): un obxecto singleton (por exemplo: OrdeCopiar(CopyCommand) só terá unha instancia), o cal coñece acerca das teclas de atallo, imaxes de botón, texto de orde etc. relacionados coa orde. Un obxecto Fonte/Invocador chama ao método executar/realizarAcción do obxecto Orde/Acción. O obxecto Orde/Acción notifica aos obxectos Fontes/Invocadores apropiados cando a dispoñibilidade dunha Orde/Acción cambiou. Isto permite aos botóns e ítems de menú inactivarse (en cor gris) cando unha Orde/Acción non pode ser executada/realizada.
    3. Receptor (Receiver), Obxecto Destino(Target Object): o obxecto está a punto de ser copiado, pegado, movido etc. O obxecto Receptor posúe o método que é chamado no método executar da orde. O receptor é tipicamente o Obxecto Destino. Por exemplo, se o obxecto Receptor é un cursor e o método se chama moverArriba espérase que o cursor sexa o obxecto de tal acción moverArriba. Por outro lado, se o código está definido polo obxecto Orde mesmo, o obxecto Destino será outro obxecto completamente diferente.
    4. Obxecto Orde (Command Object), Argumentos de Evento Enrutado (routed event args), Obxecto Evento (event object): o obxecto que é pasado da Fonte ao obxecto Orde/Acción, ao obxecto Destino ao código que fai o traballo. Cada clic de botón ou atallo de teclado resulta nun novo obxecto Orde/Evento. Algunhas implementacións poñen obxectos Ordes/Eventos noutros obxectos Eventos segundo se van movendo pola liña de execución, para evitar conflitos de nomeamentos. (Véxase tamén Chain of Responsibility (padrón de deseño)|padrón cadea de responsabilidades).
    5. Manipulador (Handler), ManipuladorDeEventoEnrutadoExecutado(ExecutedRoutedEventHandler), método(method), función(function): o código real que fai o copiado, pegado, desprazamento etc. Nalgunhas implementacións o código do manipulador é parte do obxecto Orde/Acción. Noutras implementacións é parte do obxecto Receptor/Destino, e noutras o código do manipulador é gardado separado doutros obxectos.
    6. Xestor de ordes (Command Manager), Xestor de Retrotracción (Undo Manager), Planificador (Scheduler), Cola (Queue), Resolutor(Dispatcher), Invocador(Invoker): un obxecto que pon obxectos Orde/Evento nunha pila de retrotracción ou noutra pila de repetición, ou que mantén obxectos Orde/Evento ata que outros obxectos estean preparados para actuar con eles, ou que enruta obxectos de Orde/Evento ao obxecto Receptor/Destino ou código manipulador axeitado
  3. Implementacións que exceden o padrón Orde orixinal.
    1. Windows Presentation Foundation[1] (WPF) de Microsoft, presenta ordes enrutadas, que combinan o padrón Orde co procesado de eventos. Como resultado o obxecto Orde xa non contén unha referencia ao obxecto Destino nin unha referencia ao código de aplicación. En cambio, invocar o método executar do obxecto Orde resulta en algo chamado Evento Enrutado Executado (Executed Routed event) o cal durante o tunelado ou emburbullamento do evento pode encontrar un obxecto unión (binding) que identifica o Destino ou o código de aplicación, o cal se executará entón.

Participantes

editar
  • Comando.

Interface que define os métodos executar e desfacer que se implementarán en cada clase concreta, permitindo o acceso ás ordes.

  • ComandoConcreto.

Clase que implementa unha orde concreta e os seus métodos executar e desfacer. O seu construtor debe inicializar os parámetros da orde (o seu estado).

  • Invocador.

Clase que instancia as ordes, pode á súa vez executalas inmediatamente (chamando a executar) ou deixar que o Cliente o faga.

  • Receptor.

Clase que coñece como se realizan as operacións asociadas a un comando.

  • Cliente.

Responsable de xestionar os comandos creados polo Invocador, así como a súa secuenciación e ordenación. Chama aos métodos executar e desfacer.

Consecuencias

editar
  • Independízase a parte da aplicación que invoca as ordes da implementación das mesmas.
  • Ao tratarse as ordes como obxectos, pódese realizar herdanza das mesmas, composicións de ordes (mediante o padrón Composite).
  • Facilítase a ampliación do conxunto de ordes.

Usos coñecidos

editar
Desfacer multi-nivel
Se todas as accións do usuario nun programa están implementadas coma obxectos de comando, o programa pode gardar unha pila dos comandos máis recentemente executados. Cando o usuario quere desfacer un comando, o programa simplemente saca da pila o obxecto de comando máis recentemente engadido e executa o seu método desfacer().
Comportamento transacional
Similar ao desfacer, un instalador dunha Base de Datos pode gardar unha lista das operacións que foron ou serán realizadas. Se fallase unha delas, todas as demais poderían ser revertidas (chamado normalmente rollback). Por exemplo, se dúas táboas da BD que se refiren unha á outra deben ser actualizadas, e a segunda actualización falla, a transacción pode ser botada para atrás, desfacendo a referencia non válida na primeira táboa.
Barra de progreso
Supoñamos un programa coma unha secuencia de comandos que se executan en orde. Se cada obxecto comando ten un método obterDuracionEstimada(), o programa pode facilmente estimar a duración total. Pode mostrar unha barra de progreso que reflicta de maneira significativa o preto que está o programa de completar todas as tarefas.
Asistente (software)s
Frecuentemente un asistente presenta un elevado número de páxinas de configuración para unha soa acción que ocorre só cando o usuario cliquea o botón "Finalizar" da última páxina. Nestes casos, unha forma natural de separar o código da interface de usuario do código da aplicación é implementar o asistente utilizando un obxecto comando. O obxecto comando é creado cando o asistente aparece por primeira vez. Cada páxina do asistente garda os cambios da interface no obxecto comando, polo que o obxecto vaise enchendo a medida que o usuario progresa. O "Finalizar" simplemente dispara unha chamada a executar(). Desta forma, a clase comando non contén código da interface.
Botóns da GUI e ítems de menú

Na programación de Swing (Java)|Swing e Borland Delphi, un Action[2] é un obxecto comando. Ademais da habilidade para realizar o comando desexado, unha Acción pode ter unha icona asociado, un atallo de teclado, información sobre ferramentas de texto etc. Un botón da barra de ferramentas ou un ítem de menú pode ser inicializado completamente usando só o obxecto Acción.

Piscinas de procesos
Unha piscina de procesos de propósito xeral podería ter un método engadirTarefa() público que engade un ítem de traballo a unha cola interna de tarefas esperando ser realizadas. Mantense unha piscina de procesos que executan comandos da cola. Tipicamente estes obxectos implementan unha interface común como java.lang.Runnable que lle permite á piscina de procesos executar o comando aínda que a clase da piscina de procesos mesma fose escrita sen ningún coñecemento das tarefas específicas para as que fose empregada.
Gravación de Macro (informática)
Se todas as accións de usuario son representadas por obxectos comando, un programa pode gravar unha secuencia de accións conservando simplemente unha lista dos obxectos comando conforme son executados. Entón pode volver a executar as mesmas accións executando os mesmos obxectos comando de novo en secuencia. Se o programa inclúe un motor de scripting, cada obxecto comando pode implementar un método toScript(), e as accións de usuario poden ser gardadas facilmente como scripts.
Redes de Comunicacións
É posible enviar obxectos comando enteiros pola rede para ser executados en outras máquinas, por exemplo accións de xogadores nun videoxogo.
Procesamento en Paralelo
Onde os comandos son escritos como tarefas a un recurso compartido por varios procesos en paralelo (posiblemente en máquinas remotas - esta variante é referida frecuentemente como Mestre/Escravo).
Mobilidade do código
Empregando linguaxes como Java onde o código pode ser enviado dunha localización a outra usando URLClassloaders e Codebases os comandos poden habilitar novo comportamento para ser entregado a localizacións remotas.

Exemplo

editar

Considérese un "simple" interruptor. Neste exemplo configuramos o interruptor con dúas ordes: acender a luz e apagar a luz.

Un beneficio desta implementación en particular do padrón orde é que o interruptor pode ser usado en calquera dispositivo, non só cunha luz - o interruptor no seguinte exemplo encende e apaga a luz, pero o construtor do interruptor é capaz de aceptar calquera subclase de Orde para os seus dous parámetros. Por exemplo, poderíase configurar o interruptor para acender un motor.

Nótese: Unha crítica da aplicación de exemplo é que non modeliza verdadeiramente un circuíto eléctrico. Un interruptor eléctrico é parvo. Un verdadeiro interruptor binario só coñece se está nunha posición ou outra. Non sabe nada ou non ten relación directa coas tarefas que se lle poderían asignar nun circuíto. O interruptor simplemente publica un evento do seu estado actual (aceso ou apagado). O circuíto debería entón conter unha Máquina de estados que xestione os estados en momentos dados (escoitando os eventos do interruptor) para acomodar apropiadamente complexos circuítos con tarefas e interruptores. Cada tarefa é condicional a un estado específico do circuíto (non directamente a un interruptor). En conclusión, o interruptor non debería ser consciente dos detalles da lámpada.

/* A clase Invocadora */
public class Interruptor {

    private Comando comandoSubir;
    private Comando comandoBaixar;

    public Interruptor(Comando comandoSubir, Comando comandoBaixar) {
         this.comandoSubir = comandoSubir;
         this.comandoBaixar = comandoBaixar;
    }

    public void subir() {
         comandoSubir.executar();
    }

    public void baixar() {
         comandoBaixar.executar();
    }
}

/* A clase Receptora */
public class Lampada {

     public Lampada() {  }

     public void acender() {
        System.out.println("A lámpada está acesa");
     }

     public void apagar() {
        System.out.println("A lámpada está apagada");
     }
}

/* A interface Comando */
public interface Comando {
    void executar();
}

/* O Comando para acender a luz en Norteamérica, ou apagar a luz na maioría dos demais sitios */
public class ComandoSubir implements Comando {

   private Lampada aLampada;

   public ComandoSubir(Lampada lampada) {
        this.aLampada=lampada;
   }

   public void executar(){
      aLampada.acender();
   }
}

/* O Comando para apagar a luz en Norteamérica, ou acender a luz na maioría dos demais sitios */
public class ComandoBaixar implements Comando {

   private Lampada aLampada;

   public ComandoBaixar(Lampada lampada) {
        this.aLampada=lampada;
   }

   public void executar() {
      aLampada.apagar();
   }
}

/* O cliente ou clase Test */
public class PulsarInterruptor {

   public static void main(String[] args) {
       Lampada lampada = new Lampada();
       Comando subir = new ComandoSubir(lamp);
       Comando baixar = new ComandoBaixar(lamp);

       // Véxase crítica ao modelo arriba:
       // O interruptor mesmo non debería preocuparse dos detalles da lámpada (subir, baixar)
       // nin directa nin indirectamente
       Interruptor i = new Interruptor(subir,baixar);

       try {
           if (args[0].equalsIgnoreCase("ON")) {
                i.subir();
           } else if (args[0].equalsIgnoreCase("OFF")) {
               i.baixar();
           } else {
               System.out.println("Argumento \"ON\" or \"OFF\" é requirido.");
           }
       } catch (Exception e){
           System.out.println("Arguments required.");
       }
   }
}

padróns relacionados

editar

Ofrece unha forma alternativa de chamar ás ordes ademais do uso do command manager.

Pódese implementar un pequeno Intérprete mediante clases Comando.

Pode servir para implementar a lóxica de Desfacer de forma un tanto automática ou a alto nivel.

Permite realizar agrupacións de ordes de forma similar a unha macro.

Hai quen o emprega para implementar a copia da orde ao histórico de ordes.

Pode manter o estado que require o comando para desfacer o seu efecto.