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:
-
Command (interface): declara
execute()e, quando undo é necessário,undo(). Todos os comandos concretos implementam esta interface — o Invoker só conhece esse contrato. -
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óprioexecute(). -
Receiver: o objeto que sabe realizar a operação real —
o "músculo" do padrão. Ex.:
TextoEditorcom métodosinserir()edeletar(). O Receiver contém a lógica de domínio; o Command apenas a orquestra. -
Invoker: dispara o comando via
execute()e mantém o histórico. Não conhece os tipos concretos — só a interfaceCommand. 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'
<?php
// ── Command interface ─────────────────────────────────────────
interface Command
{
public function execute(): void;
public function undo(): void;
}
// ── Receiver ──────────────────────────────────────────────────
class TextoEditor
{
private string $conteudo = '';
public function inserir(string $texto, int $posicao): void
{
$this->conteudo =
substr($this->conteudo, 0, $posicao)
. $texto
. substr($this->conteudo, $posicao);
}
public function deletar(int $posicao, int $comprimento): void
{
$this->conteudo =
substr($this->conteudo, 0, $posicao)
. substr($this->conteudo, $posicao + $comprimento);
}
public function getConteudo(): string
{
return $this->conteudo;
}
}
// ── ConcreteCommands ──────────────────────────────────────────
class InserirTextoCommand implements Command
{
public function __construct(
private readonly TextoEditor $editor,
private readonly string $texto,
private readonly int $posicao
) {}
public function execute(): void
{
$this->editor->inserir($this->texto, $this->posicao);
}
public function undo(): void
{
$this->editor->deletar($this->posicao, strlen($this->texto));
}
}
class DeletarTextoCommand implements Command
{
private string $textoRemovido = '';
public function __construct(
private readonly TextoEditor $editor,
private readonly int $posicao,
private readonly int $comprimento
) {}
public function execute(): void
{
// Salva o trecho ANTES de remover — necessário para undo.
$this->textoRemovido = substr(
$this->editor->getConteudo(),
$this->posicao,
$this->comprimento
);
$this->editor->deletar($this->posicao, $this->comprimento);
}
public function undo(): void
{
$this->editor->inserir($this->textoRemovido, $this->posicao);
}
}
// ── Invoker ───────────────────────────────────────────────────
class HistoricoComandos
{
/** @var Command[] */
private array $historico = [];
private int $indice = -1;
public function executar(Command $comando): void
{
array_splice($this->historico, $this->indice + 1);
$this->historico[] = $comando;
$this->indice++;
$comando->execute();
}
public function undo(): bool
{
if ($this->indice < 0) return false;
$this->historico[$this->indice]->undo();
$this->indice--;
return true;
}
public function redo(): bool
{
if ($this->indice >= count($this->historico) - 1) return false;
$this->indice++;
$this->historico[$this->indice]->execute();
return true;
}
}
// ── Uso ──────────────────────────────────────────────────────
$editor = new TextoEditor();
$historico = new HistoricoComandos();
$historico->executar(new InserirTextoCommand($editor, 'Olá', 0));
echo $editor->getConteudo() . "\n"; // Olá
$historico->executar(new InserirTextoCommand($editor, ' mundo', 3));
echo $editor->getConteudo() . "\n"; // Olá mundo
$historico->executar(new DeletarTextoCommand($editor, 3, 6));
echo $editor->getConteudo() . "\n"; // Olá
$historico->undo();
echo $editor->getConteudo() . "\n"; // Olá mundo
$historico->undo();
echo $editor->getConteudo() . "\n"; // Olá
$historico->redo();
echo $editor->getConteudo() . "\n"; // 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
<?php
// Interface Command (mesma do Exemplo 1).
interface Command
{
public function execute(): void;
public function undo(): void;
}
// ── Receiver ─────────────────────────────────────────────────
class Luz
{
private bool $ligada = false;
private int $intensidade = 100;
public function ligar(): void
{
$this->ligada = true;
echo "Luz ligada ({$this->intensidade}%)\n";
}
public function desligar(): void
{
$this->ligada = false;
echo "Luz desligada\n";
}
public function setIntensidade(int $pct): void
{
$anterior = $this->intensidade;
$this->intensidade = $pct;
echo "Intensidade: {$anterior}% → {$pct}%\n";
}
public function getIntensidade(): int
{
return $this->intensidade;
}
}
// ── ConcreteCommands ──────────────────────────────────────────
class LigarLuzCommand implements Command
{
public function __construct(private readonly Luz $luz) {}
public function execute(): void { $this->luz->ligar(); }
public function undo(): void { $this->luz->desligar(); }
}
class DimmerCommand implements Command
{
private int $intensidadeAnterior = 0;
public function __construct(
private readonly Luz $luz,
private readonly int $novaIntensidade
) {}
public function execute(): void
{
$this->intensidadeAnterior = $this->luz->getIntensidade();
$this->luz->setIntensidade($this->novaIntensidade);
}
public function undo(): void
{
$this->luz->setIntensidade($this->intensidadeAnterior);
}
}
class MacroCommand implements Command
{
/** @param Command[] $comandos */
public function __construct(private readonly array $comandos) {}
public function execute(): void
{
foreach ($this->comandos as $c) {
$c->execute();
}
}
public function undo(): void
{
foreach (array_reverse($this->comandos) as $c) {
$c->undo();
}
}
}
// ── Invoker ───────────────────────────────────────────────────
class ControleRemoto
{
/** @var Command[] */
private array $pilha = [];
public function pressionar(Command $comando): void
{
$comando->execute();
$this->pilha[] = $comando;
}
public function desfazer(): void
{
$ultimo = array_pop($this->pilha);
$ultimo?->undo();
}
}
// ── Uso ──────────────────────────────────────────────────────
$luz = new Luz();
$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%
$modoCinema = new MacroCommand([
new LigarLuzCommand($luz),
new DimmerCommand($luz, 10),
]);
$controle->pressionar($modoCinema);
// Luz ligada (100%)
// Intensidade: 100% → 10%
$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.