Padrão Comportamental (GoF)

Command

Encapsula uma requisição como objeto, desacoplando quem invoca a ação de quem a executa — e permitindo enfileirar, logar, desfazer (undo) e refazer (redo) operações por meio da mesma interface.

Intenção

Representar uma ação como um objeto autônomo que encapsula a operação, o receptor (Receiver) e todos os parâmetros necessários para executá-la e, opcionalmente, desfazê-la. O objeto que dispara a ação (Invoker) conhece apenas a interface Command — não sabe quem executa nem como.

Catalogado pelo GoF (1994) como padrão comportamental, o Command aparece em sistemas de edição (undo/redo), filas de tarefas, transações, menus de aplicação e pipelines de processamento. Em arquiteturas modernas está presente no dispatch de ações (Redux/Flux), em event sourcing e em sistemas de automação onde ações precisam ser persistidas, reproduzidas ou revertidas.

Problema

Imagine um editor de texto onde botões da barra de ferramentas executam operações no documento. A abordagem direta acopla cada botão ao editor e impede qualquer mecanismo de desfazer:

// Abordagem ingênua — NÃO faça isso:
class BotaoInserir {
  constructor(private editor: TextoEditor) {}

  clicar(texto: string, posicao: number): void {
    // Acoplamento direto ao editor. Não há histórico, não há undo.
    this.editor.inserir(texto, posicao);
  }
}

class BotaoDeletar {
  constructor(private editor: TextoEditor) {}

  clicar(posicao: number, comprimento: number): void {
    // Idem: cada botão precisa conhecer a API interna do editor.
    this.editor.deletar(posicao, comprimento);
  }
}

Problemas imediatos: não há como implementar Ctrl+Z porque não existe onde armazenar "o que foi feito e como reverter"; cada botão ou atalho de teclado duplica a lógica de chamada; adicionar novos tipos de ação exige criar mais componentes acoplados ao editor. Testes também ficam mais difíceis porque não é possível testar o histórico de ações independentemente.

O Command resolve isso transformando cada ação em um objeto imutável que carrega o receptor, os parâmetros e o estado necessário para se desfazer. O Invoker mantém uma pilha desses objetos e implementar undo/redo passa a ser trivial — sem modificar nenhum botão, nenhum atalho e nenhum componente de domínio.

Solução

O Command organiza o código em quatro participantes:

  1. Command (interface): declara execute() e, quando undo é necessário, undo(). Todos os comandos concretos implementam esta interface — o Invoker só conhece esse contrato.
  2. ConcreteCommand: captura no construtor o Receiver e os parâmetros necessários. execute() delega ao Receiver; undo() reverte a operação, geralmente salvando o estado anterior durante o próprio execute().
  3. Receiver: o objeto que sabe realizar a operação real — o "músculo" do padrão. Ex.: TextoEditor com métodos inserir() e deletar(). O Receiver contém a lógica de domínio; o Command apenas a orquestra.
  4. Invoker: dispara o comando via execute() e mantém o histórico. Não conhece os tipos concretos — só a interface Command. Implementa undo/redo percorrendo a pilha de comandos.

A separação entre Invoker e Receiver é o benefício central: um botão de UI, um atalho de teclado, uma API REST e um script de automação podem invocar o mesmo Command sem nenhum conhecimento sobre o TextoEditor.

Estrutura

         «interface»
           Command
  ┌───────────────────────────────┐
  │ + execute(): void             │
  │ + undo(): void                │
  └───────────────────────────────┘
              ▲
   ┌──────────┴──────────────────────┐
   │                                 │
InserirTextoCommand         DeletarTextoCommand
(ConcreteCommand)           (ConcreteCommand)
  │                           │
  │ usa                       │ usa
  ▼                           ▼
            TextoEditor (Receiver)
  ┌──────────────────────────────────────┐
  │ + inserir(texto, posicao): void      │
  │ + deletar(posicao, comprimento): void│
  │ + getConteudo(): string              │
  └──────────────────────────────────────┘


HistoricoComandos (Invoker)
  ┌──────────────────────────────────────────┐
  │ - historico: Command[]                   │
  │ - indice: number                         │
  │ + executar(cmd: Command): void           │
  │ + undo(): boolean                        │
  │ + redo(): boolean                        │
  └──────────────────────────────────────────┘
              │ invoca execute() / undo()
              ▼
           Command


Fluxo típico (undo):

  historico.executar(new InserirTextoCommand(editor, "Olá", 0))
    → InserirTextoCommand.execute()
      → editor.inserir("Olá", 0)   → conteudo: "Olá"

  historico.undo()
    → InserirTextoCommand.undo()
      → editor.deletar(0, 3)        → conteudo: ""

Exemplos de código

Exemplo 1 — Editor de texto com histórico de undo/redo

Implementação completa com a interface Command, dois comandos concretos que capturam o estado necessário para se desfazer, o Receiver (TextoEditor) e o Invoker (HistoricoComandos) com suporte completo a undo e redo. Note como DeletarTextoCommand salva o trecho removido durante execute() — fundamental para um undo correto.

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

// ── Receiver ──────────────────────────────────────────────────
class TextoEditor {
  private conteudo: string = '';

  inserir(texto: string, posicao: number): void {
    this.conteudo =
      this.conteudo.slice(0, posicao) + texto + this.conteudo.slice(posicao);
  }

  deletar(posicao: number, comprimento: number): void {
    this.conteudo =
      this.conteudo.slice(0, posicao) +
      this.conteudo.slice(posicao + comprimento);
  }

  getConteudo(): string { return this.conteudo; }
}

// ── ConcreteCommands ──────────────────────────────────────────
class InserirTextoCommand implements Command {
  constructor(
    private readonly editor: TextoEditor,
    private readonly texto: string,
    private readonly posicao: number
  ) {}

  execute(): void {
    this.editor.inserir(this.texto, this.posicao);
  }

  // Undo: deleta exatamente o trecho que foi inserido.
  undo(): void {
    this.editor.deletar(this.posicao, this.texto.length);
  }
}

class DeletarTextoCommand implements Command {
  // Estado capturado durante execute() — essencial para undo correto.
  private textoRemovido: string = '';

  constructor(
    private readonly editor: TextoEditor,
    private readonly posicao: number,
    private readonly comprimento: number
  ) {}

  execute(): void {
    // Salva o trecho ANTES de remover.
    this.textoRemovido = this.editor
      .getConteudo()
      .slice(this.posicao, this.posicao + this.comprimento);
    this.editor.deletar(this.posicao, this.comprimento);
  }

  // Undo: reinsere o trecho salvo na posição original.
  undo(): void {
    this.editor.inserir(this.textoRemovido, this.posicao);
  }
}

// ── Invoker ───────────────────────────────────────────────────
class HistoricoComandos {
  private readonly historico: Command[] = [];
  private indice: number = -1;

  executar(comando: Command): void {
    // Descarta o histórico à frente do cursor (elimina redo pendente).
    this.historico.splice(this.indice + 1);
    this.historico.push(comando);
    this.indice++;
    comando.execute();
  }

  undo(): boolean {
    if (this.indice < 0) return false;
    this.historico[this.indice].undo();
    this.indice--;
    return true;
  }

  redo(): boolean {
    if (this.indice >= this.historico.length - 1) return false;
    this.indice++;
    this.historico[this.indice].execute();
    return true;
  }
}

// ── Uso ──────────────────────────────────────────────────────
const editor    = new TextoEditor();
const historico = new HistoricoComandos();

historico.executar(new InserirTextoCommand(editor, 'Olá', 0));
console.log(editor.getConteudo());   // 'Olá'

historico.executar(new InserirTextoCommand(editor, ' mundo', 3));
console.log(editor.getConteudo());   // 'Olá mundo'

historico.executar(new DeletarTextoCommand(editor, 3, 6));
console.log(editor.getConteudo());   // 'Olá'

historico.undo();
console.log(editor.getConteudo());   // 'Olá mundo'

historico.undo();
console.log(editor.getConteudo());   // 'Olá'

historico.redo();
console.log(editor.getConteudo());   // 'Olá mundo'

Exemplo 2 — Controle remoto com MacroCommand

O Command também permite compor múltiplos comandos em um único MacroCommand — executando e desfazendo um conjunto de ações como se fossem uma unidade atômica. Isso demonstra a composabilidade do padrão: um MacroCommand é um Command que contém outros Commands, aplicando undo em ordem reversa.

// Interface Command (mesma do Exemplo 1).
interface Command {
  execute(): void;
  undo(): void;
}

// ── Receiver: dispositivos ────────────────────────────────────
class Luz {
  private ligada = false;
  private intensidade = 100;

  ligar(): void    { this.ligada = true;  console.log(`Luz ligada (${this.intensidade}%)`); }
  desligar(): void { this.ligada = false; console.log('Luz desligada'); }

  setIntensidade(pct: number): void {
    const anterior = this.intensidade;
    this.intensidade = pct;
    console.log(`Intensidade: ${anterior}% → ${pct}%`);
  }

  getIntensidade(): number { return this.intensidade; }
}

// ── ConcreteCommands ──────────────────────────────────────────
class LigarLuzCommand implements Command {
  constructor(private readonly luz: Luz) {}
  execute(): void { this.luz.ligar(); }
  undo(): void    { this.luz.desligar(); }
}

class DimmerCommand implements Command {
  private intensidadeAnterior = 0;

  constructor(
    private readonly luz: Luz,
    private readonly novaIntensidade: number
  ) {}

  execute(): void {
    this.intensidadeAnterior = this.luz.getIntensidade();
    this.luz.setIntensidade(this.novaIntensidade);
  }

  undo(): void {
    this.luz.setIntensidade(this.intensidadeAnterior);
  }
}

// MacroCommand: compõe vários Commands em um único objeto Command.
class MacroCommand implements Command {
  constructor(private readonly comandos: Command[]) {}

  execute(): void {
    this.comandos.forEach(c => c.execute());
  }

  // Undo em ordem reversa — desfaz o último subcomando primeiro.
  undo(): void {
    [...this.comandos].reverse().forEach(c => c.undo());
  }
}

// ── Invoker: controle remoto com pilha de undo ────────────────
class ControleRemoto {
  private readonly pilha: Command[] = [];

  pressionar(comando: Command): void {
    comando.execute();
    this.pilha.push(comando);
  }

  desfazer(): void {
    const ultimo = this.pilha.pop();
    ultimo?.undo();
  }
}

// ── Uso ──────────────────────────────────────────────────────
const luz      = new Luz();
const controle = new ControleRemoto();

controle.pressionar(new LigarLuzCommand(luz));
// Luz ligada (100%)

controle.pressionar(new DimmerCommand(luz, 40));
// Intensidade: 100% → 40%

controle.desfazer();
// Intensidade: 40% → 100%

// Macro: "modo cinema" — acende e escurece como uma ação única.
const modoCinema = new MacroCommand([
  new LigarLuzCommand(luz),
  new DimmerCommand(luz, 10),
]);

controle.pressionar(modoCinema);
// Luz ligada (100%)
// Intensidade: 100% → 10%

// Desfaz o macro inteiro em ordem reversa.
controle.desfazer();
// Intensidade: 10% → 100%
// Luz desligada

Quando usar

  • Quando você precisa de undo/redo: o Command captura tudo que é necessário para reverter uma operação — o caso de uso central do padrão em editores, planilhas e qualquer sistema de edição interativa.
  • Quando ações precisam ser enfileiradas ou agendadas: filas de tarefas, schedulers e sistemas de retry armazenam objetos Command para execução posterior ou repetida — sem precisar saber o que cada um faz.
  • Para auditoria e log de ações: cada Command é um registro estruturado da ação, seus parâmetros e timestamp. Em sistemas financeiros ou de compliance, isso fornece um histórico auditável de operações.
  • Para desacoplar UI do domínio: botões, atalhos de teclado e APIs REST podem invocar os mesmos Commands sem saber nada sobre o Receiver — a lógica de "o que fazer" fica centralizada no Command.
  • Para compor ações em macros ou transações usando MacroCommand — um conjunto de operações que deve ser executado e desfeito como uma unidade atômica.

Quando evitar

  • Quando a ação é simples, sem undo e sem fila: criar uma interface e uma classe por ação para algo como "imprimir relatório" ou "enviar e-mail" sem necessidade de reverter é overengineering. Uma chamada direta ou uma closure é suficiente.
  • Quando a explosão de classes se torna impraticável: sistemas com 50 tipos de ação geram 50 classes de Command. Em TypeScript, closures ou funções de primeira classe são uma alternativa viável para Commands simples sem estado de undo.
  • Quando o undo é inviável ou irrelevante: operações como "enviar SMS" ou "debitar conta bancária" têm efeitos colaterais irreversíveis. Implementar undo real nesses casos exigiria compensações complexas (estorno, mensagem de cancelamento) — e isso geralmente é domínio de negócio, não de padrão de design.

Prós e contras

Prós

  • Desacopla o Invoker do Receiver — quem dispara a ação não precisa saber como ela é realizada.
  • Undo/redo implementado no Invoker, sem modificar nenhum componente de UI ou de domínio.
  • Facilita filas de tarefas, log de auditoria, retry e agendamento de operações.
  • MacroCommand permite compor operações atômicas sem alterar os Commands existentes.
  • Segue o Princípio Aberto/Fechado — novos tipos de ação não exigem modificar o Invoker.

Contras

  • Aumenta o número de classes — uma classe por tipo de ação pode crescer rapidamente.
  • Para undo correto, cada Command precisa capturar e gerenciar estado anterior — o que pode ser complexo para operações que afetam múltiplos objetos.
  • O Invoker pode acumular memória com histórico ilimitado — é necessário definir um limite de profundidade.
  • Para Commands simples sem estado, criar uma classe completa é mais verboso do que uma closure ou função.

Armadilhas comuns

1. Comando anêmico (só repassa a chamada)

O erro mais comum ao aprender o padrão: criar um Command que não encapsula nada além de uma chamada direta ao Receiver, sem capturar parâmetros no construtor e sem implementar undo real. Um SalvarCommand que apenas chama repositorio.salvar(objetoExterno) sem fixar o objeto no construtor não é um Command — é boilerplate inútil.

Regra prática: se o Command não captura tudo que precisa para executar e desfazer de forma autônoma, ele é anêmico. O construtor deve fixar Receiver + parâmetros; execute() não deve receber argumentos.

2. Undo mal implementado (estado não capturado)

A armadilha técnica mais crítica: implementar execute() sem capturar o estado anterior necessário para undo(). Um DeletarTextoCommand que remove o trecho sem primeiro salvá-lo em this.textoRemovido perde permanentemente a capacidade de reverter. A regra de ouro: capture tudo que undo() precisará antes de executar a operação destrutiva.

3. Explosão de classes de Command

Em sistemas com muitos tipos de ação, o número de classes de Command cresce proporcional às ações. Quando isso ocorre e os Commands não têm estado de undo, closures são mais idiomáticos. Em TypeScript:

// Alternativa funcional para Commands simples sem estado:
type SimpleCommand = () => void;

const fila: SimpleCommand[] = [];
fila.push(() => console.log('Tarefa A'));
fila.push(() => console.log('Tarefa B'));
fila.forEach(cmd => cmd());

// Reserve classes de Command para quando undo, log ou composição são necessários.

4. Command vs Strategy — distinção de intenção

A confusão mais frequente entre os dois padrões. O Command encapsula uma ação específica com ciclo de vida definido: criação → execução → (possível) desfazimento. O objeto representa o "o que fazer e com quais dados" — ele pode ser armazenado, enfileirado e revertido. A Strategy encapsula um algoritmo intercambiável sem identidade ou ciclo de vida — o contexto troca a estratégia ativa, mas não armazena estratégias como histórico. Use Command quando a ação em si é um dado de primeira classe; use Strategy quando apenas a variação do algoritmo importa.

Padrões relacionados

O Command interage com padrões que complementam undo, enfileiramento e coordenação de ações:

O Strategy e o Command encapsulam comportamento em objetos, mas com intenções distintas: Strategy troca algoritmos intercambiáveis no contexto (sem ciclo de vida de execute/undo); Command representa ações discretas que podem ser armazenadas, enfileiradas e revertidas. O Mediator pode usar Commands para representar as ações coordenadas entre componentes — o Mediator recebe um Command e decide para qual Receiver encaminhá-lo, sem que os colegas se conheçam diretamente. O Memento é o complemento natural do Command para undo complexo: quando o estado do Receiver é rico demais para ser capturado em cada Command individualmente, o Memento cria um snapshot completo do objeto antes da operação, e o Command apenas armazena esse snapshot para restaurá-lo no undo.