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.
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.
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.
- Botão / atalho
- cria com os dadosColarTexto
- registra na pilhaHistórico
- executar() aplicaDocumento
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.
Navegue pelas camadas
Camada 1/3Invocador
use ← →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.
Estrutura
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 anteriorfrom abc import ABC, abstractmethod
class Comando(ABC):
@abstractmethod
def executar(self): ...
@abstractmethod
def desfazer(self): ...
class Documento:
def __init__(self):
self.texto = ""
# A acao vira objeto: guarda o que precisa para se desfazer
class ColarTexto(Comando):
def __init__(self, doc, trecho, posicao):
self._doc, self._trecho, self._pos = doc, trecho, posicao
def executar(self):
d = self._doc
d.texto = d.texto[: self._pos] + self._trecho + d.texto[self._pos :]
def desfazer(self):
d = self._doc
fim = self._pos + len(self._trecho)
d.texto = d.texto[: self._pos] + d.texto[fim:]
# O invocador nao conhece nenhum comando concreto
class Historico:
def __init__(self):
self._pilha = []
def executar(self, cmd):
cmd.executar()
self._pilha.append(cmd)
def desfazer(self):
if self._pilha:
self._pilha.pop().desfazer()
doc, h = Documento(), Historico()
h.executar(ColarTexto(doc, "ola", 0))
h.desfazer()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.
- 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
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
Empilhar no invocador
Histórico, fila ou botão conhecem só a interface `Comando`. Disparam sem saber qual concreta está na mão.
- 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.
Armadilhas
Desfazer assimétrico
O bug mais comum e mais caro:
executaredesfazerque 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.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
desfazerem 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.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.
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
- MementoCaptura o estado interno de um objeto num instantâneo opaco que pode ser guardado e restaurado depois, sem expor os detalhes desse estado a quem o guarda.
- ObserverDefine uma dependência um-para-muitos: quando um objeto muda de estado, todos os seus dependentes são notificados automaticamente.
- SagaCoordena uma transação distribuída de longa duração como uma sequência de transações locais, cada uma com uma ação compensatória para desfazer semanticamente em caso de falha.