Pular para o conteúdo principal

Command

Descrição​

O Command encapsula uma solicitação como um objeto, contendo tudo o que é necessário para executá-la: a ação, o objeto que a realiza (o receptor) e os parâmetros. Com isso, é possível parametrizar clientes com diferentes solicitações, enfileirá-las, registrá-las em log e suportar operações que podem ser desfeitas.

O padrão separa quem dispara a operação (o Invoker, como um botão ou um agendador) de quem sabe realizá-la (o Receiver). O Invoker conhece apenas a interface Command com o método execute(); cada comando concreto delega o trabalho ao receptor apropriado. Opcionalmente, o comando guarda o estado necessário para implementar undo().

Como os comandos são objetos de primeira classe, podem ser armazenados em histórico, compostos em macros, serializados para execução posterior ou em outro processo e reexecutados (por exemplo, para recuperação após falhas).

Problemas que resolve​

  • Acoplamento direto entre o elemento de interface (botão, menu, atalho) e a lógica de negócio que ele dispara
  • Necessidade de desfazer/refazer operações mantendo um histórico de ações
  • Enfileirar, agendar ou executar operações de forma assíncrona ou remota
  • Registrar operações em log para auditoria ou para reaplicá-las após uma falha
  • Agrupar várias operações em uma única ação composta (macro ou transação)

Quando usar​

  • Quando é preciso parametrizar objetos com ações a executar (callbacks orientados a objetos)
  • Quando as solicitações devem ser especificadas, enfileiradas e executadas em momentos diferentes
  • Quando o sistema precisa suportar undo/redo
  • Quando operações devem ser registradas e reexecutáveis (journaling, event sourcing simplificado)
  • Quando diferentes gatilhos (botão, atalho, menu, API) devem disparar a mesma operação

Quando evitar​

  • Quando a operação é simples, síncrona e chamada de um único lugar: uma chamada direta de método ou uma função/lambda basta
  • Quando não há necessidade de histórico, fila, log ou desacoplamento entre emissor e receptor
  • Quando o número de classes de comando cresceria muito sem benefício real de reutilização

Trade-offs​

GanhoCusto / Risco
Desacopla quem invoca de quem executaAumenta o número de classes (uma por operação)
Permite undo/redo, filas, logs e macrosImplementar undo() corretamente exige guardar estado e pode ser complexo
Novos comandos são adicionados sem alterar código existente (Open/Closed)Indireção extra dificulta seguir o fluxo de execução
Comandos podem ser serializados e executados depois ou remotamenteComandos serializados exigem versionamento e cuidado com receptores ausentes
  • Acoplamento: o Invoker depende apenas da abstração Command; o acoplamento ao Receiver fica concentrado nos comandos concretos.
  • Testabilidade: comandos são unidades pequenas e isoladas, fáceis de testar com receptores falsos.
  • Desempenho: guardar histórico ilimitado para undo consome memória; costuma-se limitar o tamanho ou usar Memento para snapshots.
  • Linguagens modernas: em linguagens com funções de primeira classe, closures podem substituir comandos simples, mas perdem undo e metadados.

Diagrama​

O Client cria o ConcreteCommand e o associa a um Receiver; o Invoker armazena comandos e chama execute() sem conhecer o receptor. O ConcreteCommand traduz a chamada em uma ou mais ações sobre o Receiver e pode guardar estado para undo().

Exemplo​

Um editor de texto em que ações como "inserir texto" podem ser desfeitas com Ctrl+Z. Cada ação vira um comando empilhado em um histórico.

interface Command { execute(): void; undo(): void; }

class TextEditor { // Receiver
content = "";
insert(text: string) { this.content += text; }
remove(length: number) { this.content = this.content.slice(0, -length); }
}

class InsertTextCommand implements Command {
constructor(private editor: TextEditor, private text: string) {}
execute() { this.editor.insert(this.text); }
undo() { this.editor.remove(this.text.length); }
}

class CommandManager { // Invoker
private history: Command[] = [];
run(cmd: Command) { cmd.execute(); this.history.push(cmd); }
undo() { this.history.pop()?.undo(); }
}

const editor = new TextEditor();
const manager = new CommandManager();
manager.run(new InsertTextCommand(editor, "Hello"));
manager.run(new InsertTextCommand(editor, ", world"));
manager.undo();
console.log(editor.content); // "Hello"

Padrões relacionados​

  • Memento — pode guardar o estado do receptor antes da execução para viabilizar o undo()
  • Composite — permite montar macros, comandos compostos por outros comandos
  • Chain of Responsibility — também desacopla emissor e receptor; os handlers podem ser representados como comandos
  • Strategy — estrutura parecida, mas Strategy varia como algo é feito, enquanto Command representa o que fazer como objeto
  • Prototype — útil para copiar comandos antes de colocá-los no histórico
  • Observer — comandos podem notificar observadores ou ser disparados por eventos

Referência​