Padrão de comando - Command pattern
Na programação orientada a objetos , o padrão de comando é um padrão de design comportamental no qual um objeto é usado para encapsular todas as informações necessárias para executar uma ação ou acionar um evento posteriormente. Essas informações incluem o nome do método, o objeto que possui o método e os valores dos parâmetros do método.
Quatro termos sempre associados ao padrão de comando são comando , receptor , invocador e cliente . Um objeto de comando conhece o receptor e invoca um método do receptor. Os valores dos parâmetros do método do receptor são armazenados no comando. O objeto receptor para executar esses métodos também é armazenado no objeto de comando por agregação . O receptor então faz o trabalho quando o execute()método no comando é chamado. Um objeto invocador sabe como executar um comando e, opcionalmente, guarda a contabilidade da execução do comando. O invocador não sabe nada sobre um comando concreto, ele sabe apenas sobre a interface do comando . Objeto (s) de invocador , objetos de comando e objetos de receptor são mantidos por um objeto de cliente , o cliente decide quais objetos de receptor ele atribui aos objetos de comando e quais comandos ele atribui ao invocador. O cliente decide quais comandos executar em quais pontos. Para executar um comando, ele passa o objeto de comando para o objeto invocador.
O uso de objetos de comando facilita a construção de componentes gerais que precisam delegar, sequenciar ou executar chamadas de método em um momento de sua escolha, sem a necessidade de conhecer a classe do método ou os parâmetros do método. A utilização de um objeto invocador permite que a escrituração das execuções de comandos seja realizada de forma conveniente, bem como a implementação de diferentes modos de comando, que são gerenciados pelo objeto invocador, sem a necessidade do cliente saber da existência de escrituração ou modos.
As idéias centrais desse padrão de design espelham de perto a semântica das funções de primeira classe e funções de ordem superior em linguagens de programação funcional . Especificamente, o objeto invocador é uma função de ordem superior da qual o objeto de comando é um argumento de primeira classe.
Visão geral
O padrão de design de comando é um dos vinte e três padrões de design GoF bem conhecidos que descrevem como resolver problemas de design recorrentes para projetar software orientado a objetos flexível e reutilizável, ou seja, objetos que são mais fáceis de implementar, alterar, testar e reuso.
Usar o padrão de design de comando pode resolver estes problemas:
- Deve-se evitar o acoplamento do invocador de uma solicitação a uma solicitação específica. Ou seja, as solicitações cabeadas devem ser evitadas.
- Deve ser possível configurar um objeto (que invoca uma solicitação) com uma solicitação.
Implementar (hard-wiring) uma solicitação diretamente em uma classe é inflexível porque acopla a classe a uma solicitação particular em tempo de compilação, o que torna impossível especificar uma solicitação em tempo de execução.
Usar o padrão de design de comando descreve a seguinte solução:
- Defina objetos separados (de comando) que encapsulam uma solicitação.
- Uma classe delega uma solicitação a um objeto de comando em vez de implementar uma solicitação específica diretamente.
Isso permite configurar uma classe com um objeto de comando que é usado para realizar uma solicitação. A classe não está mais acoplada a uma solicitação específica e não tem conhecimento (é independente) de como a solicitação é realizada.
Consulte também a classe UML e o diagrama de sequência abaixo.
Estrutura
Classe UML e diagrama de sequência
No diagrama de classes UML acima , a Invokerclasse não implementa uma solicitação diretamente. Em vez disso, Invokerrefere-se à Commandinterface para realizar uma solicitação ( command.execute()), o que torna Invokerindependente de como a solicitação é realizada. A Command1classe implementa a Commandinterface executando uma ação em um receptor ( receiver1.action1()).
O diagrama de sequência UML
mostra as interações em tempo de execução: O Invokerobjeto chama execute()um Command1objeto.
Command1chama action1()um Receiver1objeto, que executa a solicitação.
Diagrama de classe UML
Usos
- Botões GUI e itens de menu
- Na programação Swing e Borland Delphi , an
Actioné um objeto de comando. Além da capacidade de executar o comando desejado, uma Ação pode ter um ícone associado, atalho de teclado, texto de dica de ferramenta e assim por diante. Um botão da barra de ferramentas ou componente de item de menu pode ser completamente inicializado usando apenas o objeto Action . - Gravação macro
- Se todas as ações do usuário são representadas por objetos de comando, um programa pode registrar uma sequência de ações simplesmente mantendo uma lista dos objetos de comando à medida que são executados. Ele pode então "reproduzir" as mesmas ações executando os mesmos objetos de comando novamente em sequência. Se o programa incorporar um mecanismo de script, cada objeto de comando pode implementar um método toScript () e as ações do usuário podem ser facilmente registradas como scripts.
- Código móvel
- Usando linguagens como Java, onde o código pode ser transmitido / slurped de um local para outro por meio de URLClassloaders e Codebases, os comandos podem permitir que um novo comportamento seja entregue a locais remotos (Comando EJB, Master Worker).
- Desfazer multinível
- Se todas as ações do usuário em um programa forem implementadas como objetos de comando, o programa pode manter uma pilha dos comandos executados mais recentemente. Quando o usuário deseja desfazer um comando, o programa simplesmente exibe o objeto de comando mais recente e executa seu método undo () .
- Networking
- É possível enviar objetos de comando inteiros pela rede para serem executados em outras máquinas, por exemplo, ações do jogador em jogos de computador.
- Processamento paralelo
- Onde os comandos são escritos como tarefas para um recurso compartilhado e executados por muitos threads em paralelo (possivelmente em máquinas remotas; esta variante é frequentemente referida como o padrão Mestre / Trabalhador)
- Barras de progresso
- Suponha que um programa tenha uma sequência de comandos que ele executa em ordem. Se cada objeto de comando tiver um método getEstimatedDuration () , o programa poderá estimar facilmente a duração total. Ele pode mostrar uma barra de progresso que reflete significativamente o quão perto o programa está de concluir todas as tarefas.
- Pools de threads
- Uma classe típica de pool de threads de uso geral pode ter um método público addTask () que adiciona um item de trabalho a uma fila interna de tarefas aguardando para serem realizadas. Ele mantém um pool de threads que executam comandos da fila. Os itens na fila são objetos de comando. Normalmente, esses objetos implementam uma interface comum, como java.lang.Runnable, que permite que o conjunto de encadeamentos execute o comando, mesmo que a própria classe do conjunto de encadeamentos tenha sido escrita sem nenhum conhecimento das tarefas específicas para as quais seria usado.
- Comportamento transacional
- Semelhante a desfazer, um mecanismo de banco de dados ou instalador de software pode manter uma lista de operações que foram ou serão realizadas. Se um deles falhar, todos os outros podem ser revertidos ou descartados (geralmente chamado de reversão ). Por exemplo, se duas tabelas de banco de dados que se referem uma à outra devem ser atualizadas e a segunda atualização falhar, a transação pode ser revertida, de forma que a primeira tabela não contenha uma referência inválida.
- Feiticeiros
- Freqüentemente, um assistente apresenta várias páginas de configuração para uma única ação que acontece apenas quando o usuário clica no botão "Concluir" na última página. Nesses casos, uma maneira natural de separar o código da interface do usuário do código do aplicativo é implementar o assistente usando um objeto de comando. O objeto de comando é criado quando o assistente é exibido pela primeira vez. Cada página do assistente armazena suas mudanças de GUI no objeto de comando, para que o objeto seja preenchido conforme o usuário avança. "Finish" simplesmente dispara uma chamada para execute () . Dessa forma, a classe de comando funcionará.
Terminologia
A terminologia usada para descrever as implementações do padrão de comando não é consistente e, portanto, pode ser confusa. Este é o resultado da ambigüidade , do uso de sinônimos e implementações que podem obscurecer o padrão original indo muito além dele.
- Ambiguidade.
- O termo comando é ambíguo. Por exemplo, mover para cima, mover para cima pode se referir a um único comando (mover para cima) que deve ser executado duas vezes, ou pode se referir a dois comandos, cada um dos quais faz a mesma coisa (mover para cima). Se o comando anterior for adicionado duas vezes a uma pilha de desfazer, os dois itens da pilha se referem à mesma instância de comando. Isso pode ser apropriado quando um comando sempre pode ser desfeito da mesma maneira (por exemplo, mover para baixo). Tanto a Gang of Four quanto o exemplo Java abaixo usam essa interpretação do termo comando . Por outro lado, se os últimos comandos forem adicionados a uma pilha de desfazer, a pilha se refere a dois objetos separados. Isso pode ser apropriado quando cada objeto na pilha deve conter informações que permitem que o comando seja desfeito. Por exemplo, para desfazer um comando de exclusão de seleção , o objeto pode conter uma cópia do texto excluído para que possa ser reinserido, se o comando de exclusão de seleção precisar ser desfeito. Observe que o uso de um objeto separado para cada chamada de um comando também é um exemplo do padrão de cadeia de responsabilidade .
- O termo executar também é ambíguo. Pode referir-se à execução do código identificado pelo método execute do objeto de comando . No entanto, no Windows Presentation Foundation da Microsoft, um comando é considerado executado quando o método execute do comando é invocado, mas isso não significa necessariamente que o código do aplicativo foi executado. Isso ocorre apenas após algum processamento adicional de eventos.
- Sinônimos e homônimos .
- Cliente, Fonte, Invocador : o botão, botão da barra de ferramentas ou item de menu clicado, a tecla de atalho pressionada pelo usuário.
- Objeto de comando, Objeto de comando roteado, Objeto de ação : um objeto singleton (por exemplo, há apenas um objeto CopyCommand), que conhece as teclas de atalho, imagens de botão, texto de comando, etc. relacionados ao comando. Um objeto de origem / invocador chama o método execute / performAction do objeto Comando / Ação. O objeto Comando / Ação notifica os objetos de origem / invocador apropriados quando a disponibilidade de um comando / ação é alterada. Isso permite que botões e itens de menu fiquem inativos (esmaecidos) quando um comando / ação não pode ser executado / realizado.
- Receptor, Objeto de destino : o objeto que está para ser copiado, colado, movido, etc. O objeto receptor possui o método que é chamado pelo método execute do comando . O receptor normalmente também é o objeto de destino. Por exemplo, se o objeto receptor for um cursor e o método for denominado moveUp , seria de se esperar que o cursor fosse o alvo da ação moveUp. Por outro lado, se o código for definido pelo próprio objeto de comando, o objeto de destino será um objeto totalmente diferente.
- Objeto de comando, argumentos de evento roteados, objeto de evento : o objeto que é passado da origem ao objeto Comando / Ação, ao objeto Destino ao código que faz o trabalho. Cada clique de botão ou tecla de atalho resulta em um novo objeto de comando / evento. Algumas implementações adicionam mais informações ao objeto de comando / evento conforme ele está sendo passado de um objeto (por exemplo, CopyCommand) para outro (por exemplo, seção de documento). Outras implementações colocam objetos de comando / evento em outros objetos de evento (como uma caixa dentro de uma caixa maior) conforme eles se movem ao longo da linha, para evitar conflitos de nomenclatura. (Veja também padrão de cadeia de responsabilidade .)
- Handler, ExecutedRoutedEventHandler, método, função : o código real que faz a cópia, colagem, movimentação, etc. Em algumas implementações, o código do manipulador é parte do objeto de comando / ação. Em outras implementações, o código é parte do Objeto Receptor / Alvo e, ainda em outras implementações, o código do manipulador é mantido separado dos outros objetos.
- Command Manager, Undo Manager, Scheduler, Queue, Dispatcher, Invoker : um objeto que coloca objetos de comando / evento em uma pilha de desfazer ou de refazer, ou que mantém os objetos de comando / evento até que outros objetos estejam prontos para agir sobre eles, ou que roteia os objetos de comando / evento para o objeto receptor / alvo ou código de manipulador apropriado.
- Implementações que vão muito além do padrão de comando original.
- O Windows Presentation Foundation (WPF) da Microsoft apresenta comandos roteados, que combinam o padrão de comando com o processamento de eventos. Como resultado, o objeto de comando não contém mais uma referência ao objeto de destino nem uma referência ao código do aplicativo. Em vez disso, invocar o comando execute do objeto de comando resulta em um evento chamado Executed Routed Event que, durante o tunelamento ou bubbling do evento, pode encontrar um chamado objeto de ligação que identifica o destino e o código do aplicativo, que é executado naquele ponto.
Exemplo
Considere uma opção "simples". Neste exemplo configuramos o Switch com dois comandos: para ligar a luz e para desligar a luz.
Um benefício dessa implementação específica do padrão de comando é que o switch pode ser usado com qualquer dispositivo, não apenas uma luz. O Switch na implementação de C # a seguir acende e apaga uma luz, mas o construtor do Switch é capaz de aceitar quaisquer subclasses de Command para seus dois parâmetros. Por exemplo, você pode configurar o Switch para iniciar um 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.");
}
}
}
}
Veja também
- Fila de lote
- Fecho
- Fila de comando
- Objeto de função
- Agendador de trabalho
- Model-view-controller
- Fila de prioridade
- Padrão de design de software
- GoF - Padrões de Design
Origens
A primeira menção publicada do uso de uma classe Command para implementar sistemas interativos parece ser um artigo de 1985 de Henry Lieberman. A primeira descrição publicada de um (de nível múltiplo) desfazer-refazer mecanismo, usando uma classe de comando com execução e desfazer métodos, e uma lista de histórico, parece ser a primeira edição (1988) de Bertrand Meyer livro de Software Orientada a Objetos Construção , seção 12.2.
