ComportamentalIntermediário7 min de leitura9 seções1994, Marco de referência: a ideia é mais velha que a data

Command

Transforma uma ação em objeto, com os dados de que ela precisa. Isso permite enfileirar, agendar, registrar e desfazer operações — e desacopla quem pede de quem executa.

Em 10 segundos

Em vez de chamar `documento.colar(texto)`, você cria um objeto `ColarTexto(documento, texto, posição)` com `executar()` e `desfazer()`. Como a ação virou valor, ela pode ser empilhada para o Ctrl+Z, posta numa fila, gravada num log ou repetida após falha.

  • #undo
  • #fila
  • #acao-como-objeto
  • #gof
Analogia

A comanda do restaurante

O garçom não vai à cozinha explicar como grelhar — ele escreve uma comanda e a prende no varal. A comanda é a ação virada papel: tem tudo que a cozinha precisa, pode esperar na fila, ser feita fora de ordem, ser reimpressa se sumir, e no fim do dia vira o histórico do movimento. O garçom não sabe cozinhar; a cozinha não sabe quem é a mesa 12.

01Conceito

O problema

Uma chamada de método é um evento efêmero: ela acontece e desaparece. Não dá para adiar, repetir, inspecionar antes de rodar nem reverter depois.

Requisitos muito comuns — Ctrl+Z, fila de jobs, retry, auditoria, macro — todos pedem a mesma coisa: tratar a intenção como dado, não como salto de execução.

Há também o acoplamento entre gatilho e execução. Sem o padrão, o handler do botão conhece o serviço, monta os argumentos e chama. Multiplique por menu, atalho de teclado e endpoint de API e você tem a mesma lógica em três lugares, divergindo com o tempo.

O Command resolve os dois de uma vez porque objetiviza a intenção. E é o mesmo raciocínio que sustenta padrões bem maiores: um evento em Event Sourcing é um comando já executado e imutável; um passo de Saga é um comando com sua compensação ao lado. Reconhecer o Command ajuda a ler essas arquiteturas.

Cuidado com uma confusão comum: em CQRS, 'Command' nomeia o lado de escrita (comando × consulta) e nem sempre implica este padrão. São ideias compatíveis, mas de escalas diferentes — uma é sobre um objeto, a outra sobre a arquitetura.

02Conceito

Como desfazer de verdade

Existem duas estratégias, e escolher errado é a principal fonte de bug do padrão: guardar a operação inversa ou guardar o estado anterior.

A operação inversa é econômica: colar guarda posição e tamanho, e desfazer recorta aquele trecho. Funciona enquanto a ação for reversível por cálculo — e falha silenciosamente quando não é (ordenar uma lista destrói a ordem original; aplicar 'maiúsculas' destrói a caixa anterior).

O estado anterior é o Memento: antes de executar, o comando guarda um instantâneo do que vai mudar, e desfazer restaura. É sempre correto e custa memória proporcional ao que foi tocado. Para edições pontuais é a escolha segura.

Na prática se mistura: comandos com inverso barato e exato usam o inverso; o resto usa memento do trecho afetado. O erro clássico é assumir que toda ação tem inverso — o teste que pega isso é executar, desfazer e comparar com o estado original byte a byte.

Como se encaixa
  1. Botão / atalho
  2. cria com os dadosColarTexto
  3. registra na pilhaHistórico
  4. executar() aplicaDocumento
O gatilho só monta o objeto; quem decide executar e guardar é o histórico. Como o comando ficou na pilha, o Ctrl+Z tem a quem pedir desfazer.
Sem o padrão × com o padrão

Com Command: a ação é um objeto

  • os três gatilhos criam o mesmo ColarTexto
  • o histórico empilha e desfaz sem conhecer comandos concretos
  • a fila pode adiar, repetir ou distribuir a execução
  • auditoria é serializar a pilha

O custo é uma classe por operação e a disciplina de manter executar/desfazer simétricos — sem isso, o Ctrl+Z corrompe o documento.

O ganho não é evitar o acoplamento em si, é fazer a intenção existir como valor. Tudo o que se pede depois — desfazer, fila, retry, log — é consequência disso.
03Anatomia

Navegue pelas camadas

Camada 1/3Invocador

Camada 1

Invocador

dispara sem saber o quê

Botão, atalho, item de menu, worker de fila ou agendador. Guarda uma referência a um Comando e chama `executar()`. É o que permite reconfigurar a interface sem tocar na lógica.

No código

botaoColar.onClick = () => historico.executar(cmd);

Se violar: invocador que faz `if (cmd instanceof ColarTexto)` voltou a conhecer os concretos e perdeu a razão de existir.

04Estrutura

Estrutura

Diagrama de classes
05Implementação

Código

interface Comando {
  executar(): void;
  desfazer(): void;
}

class Documento {
  texto = "";
}

// A acao vira objeto: carrega os dados que ela precisa para se desfazer
class ColarTexto implements Comando {
  constructor(
    private doc: Documento,
    private trecho: string,
    private posicao: number,
  ) {}

  executar(): void {
    const d = this.doc;
    d.texto = d.texto.slice(0, this.posicao) + this.trecho + d.texto.slice(this.posicao);
  }

  desfazer(): void {
    const d = this.doc;
    d.texto = d.texto.slice(0, this.posicao) + d.texto.slice(this.posicao + this.trecho.length);
  }
}

// O invocador nao conhece nenhum comando concreto
class Historico {
  private pilha: Comando[] = [];

  executar(cmd: Comando): void {
    cmd.executar();
    this.pilha.push(cmd);
  }

  desfazer(): void {
    this.pilha.pop()?.desfazer();
  }
}

const doc = new Documento();
const h = new Historico();
h.executar(new ColarTexto(doc, "olá", 0));
h.desfazer(); // volta ao estado anterior
06No mundo real

Casos de uso

Editor com desfazer ilimitado

Cenário

Um editor de texto ou de imagem precisa de Ctrl+Z e Ctrl+Y confiáveis, incluindo ações compostas como 'substituir tudo', que altera dezenas de trechos.

Como o padrão entra

Cada edição vira um comando; ações compostas viram um comando composto que executa e desfaz os filhos na ordem inversa. O histórico é uma pilha de comandos, e refazer é uma segunda pilha.

Trade-off: O histórico cresce com o uso e precisa de teto (últimas N ações) ou de snapshots periódicos. E toda operação nova nasce com a obrigação de implementar o inverso corretamente — esquecer disso só aparece quando o usuário desfaz.

Fila de jobs assíncronos

Cenário

Emitir nota fiscal, gerar relatório e enviar e-mail em massa não podem bloquear a requisição do usuário nem se perder se o processo cair.

Como o padrão entra

A requisição cria um comando serializado e o publica na fila; workers consomem e executam. Como o comando é dado, ele sobrevive a restart, pode ser repetido em caso de falha e fica no log para auditoria.

Trade-off: Reexecução exige idempotência: um comando 'cobrar cartão' repetido por retry cobra duas vezes se não houver chave de idempotência. E comandos serializados versionam mal — um deploy que muda os campos precisa saber ler os antigos que ainda estão na fila.

Ações de teclado configuráveis

Cenário

Uma IDE permite ao usuário remapear atalhos, e a mesma ação existe no menu, na paleta de comandos e num botão da barra.

Como o padrão entra

Cada ação é um comando registrado num catálogo por id. Atalhos, menus e a paleta apontam para ids; remapear é trocar a associação tecla → id, sem tocar em nenhuma implementação.

Trade-off: O catálogo vira uma superfície pública que precisa de estabilidade: renomear o id de um comando quebra os atalhos que o usuário configurou e as extensões que dependem dele.

Passo a passoComo objetivar a ação
  1. 1

    Congelar dados na criação

    Receptor, posição, texto, usuário — tudo que a operação precisa entra no construtor. Nada de reler cursor ou clipboard no desfazer.

  2. 2

    Implementar executar e desfazer

    Os dois são simétricos: desfazer deve restaurar exatamente o estado anterior. Teste com sequência aleatória e compare byte a byte.

  3. 3

    Empilhar no invocador

    Histórico, fila ou botão conhecem só a interface `Comando`. Disparam sem saber qual concreta está na mão.

  4. 4

    Disparar pelos gatilhos

    Menu, atalho e API criam o mesmo objeto de comando. Remapear é trocar quem aponta para o id — não reimplementar a ação.

O Command que relê o mundo no desfazer

Compila, roda, passa no code review. E quebra depois.

Existe executar/desfazer e uma pilha de histórico — mas o comando consulta cursor e clipboard de novo na hora de reverter. Ele não registrou o que aconteceu; só adiaria a chamada.

interface Comando {
  executar(): void;
  desfazer(): void;
}

// O comando NAO congela os dados na criacao.
class ColarTexto implements Comando {
  constructor(private doc: Documento) {}

  executar(): void {
    const pos = cursor.atual();           // le estado externo AGORA
    const trecho = clipboard.ler();
    this.doc.texto =
      this.doc.texto.slice(0, pos) + trecho + this.doc.texto.slice(pos);
  }

  desfazer(): void {
    const pos = cursor.atual();           // le DE NOVO — posicao diferente
    // recorta no lugar errado; o documento corrompe
    this.doc.texto = this.doc.texto.slice(0, pos);
  }
}

// Ctrl+Z apos mover o cursor desfaz outra coisa. Comando virou
// "chame o servico depois" — nao um registro do que aconteceu.

O que quebra

  • Após mover o cursorCtrl+Z recorta no lugar errado e o documento corrompe em silêncio.
  • Na fila assíncronaO worker executa com clipboard/usuário diferentes dos do momento da intenção — a ação muda de significado.
  • No teste de propriedadeExecutar e desfazer N vezes não devolve o estado inicial; a suíte acusa assimetria.

A saída: Trate o comando como registro imutável do que foi pedido: capture posição e trecho na criação. desfazer usa só o que está guardado no objeto — nunca o estado volátil atual.

08Cuidado

Armadilhas

  1. Desfazer assimétrico

    O bug mais comum e mais caro: executar e desfazer que não são exatamente inversos. Funciona nos testes com um passo e corrompe o documento depois de dez desfazeres seguidos. A verificação que pega isso é de propriedade — executar uma sequência aleatória, desfazer tudo e exigir que o estado final seja idêntico ao inicial.

  2. Comando que depende de estado externo

    Se o comando lê a seleção atual, o usuário logado ou o relógio no momento do desfazer em vez de ter congelado isso na criação, ele desfaz a coisa errada. Comando é um registro do que aconteceu: todos os dados relevantes entram nele no momento da criação.

  3. Uma classe por clique

    Aplicar Command a tudo produz dezenas de classes de três linhas que só repassam uma chamada, sem histórico, sem fila e sem desfazer. Se nenhuma das capacidades do padrão está em uso, o que existe é indireção pura — chamar o método direto é a resposta certa.

09Decisão

Quando usar × quando evitar

Use quando

3
  • Você precisa de desfazer/refazer ou de histórico auditável.
  • As ações precisam ser enfileiradas, adiadas ou repetidas após falha.
  • O mesmo comportamento é disparado por vários gatilhos e deve ficar num lugar só.

Evite quando

3
  • A ação é uma chamada direta sem histórico, fila ou reversão.
  • Desfazer exigiria guardar mais estado do que a operação vale.
  • As ações mudam de assinatura com frequência e ficam serializadas em filas de longa duração.

Testar o que ficou

3 armadilhas de Command viram perguntas, com o nome do padrão escondido, misturadas com padrões parecidos.

Onde isto aparece de verdade

Você já usa isto, talvez sem saber o nome.

  • Ações do ReduxUm objeto que descreve o que fazer, sem executar nada — logável, serializável e reexecutável.
  • Filas de jobO trabalho vira dado, atravessa a fila e é executado por outro processo em outro momento.
  • Undo do editorCada operação sabe se desfazer, e a pilha de comandos é o histórico.

O padrão em uma linha

Sem nome de domínio, sem cerimônia. Só a ideia.

// Ação vira objeto: executar e desfazer.
historico.push(new ApagarTexto(sel));
historico.at(-1).executar();

O que ele cobra

1 salto a mais entre a chamada e o efeito.

  • Cada ação vira um objeto com ciclo de vida próprio, em vez de uma chamada direta
  • Suportar undo exige guardar estado suficiente para reverter, o que nem toda ação torna simples

Não vale se: a ação não precisa ser enfileirada, desfeita nem registrada — sem isso, chamar o método direto é mais simples.

Costuma ser confundido com

Onde isto aparece

Continue por aqui