Pular para o conteúdo

Command

Encapsular uma requisição como um objeto, permitindo parametrizar clientes com diferentes requisições, enfileirar ou registrar requisições e suportar operações que podem ser desfeitas.

O invocador de uma operação (um botão, um item de menu, um job) está diretamente acoplado ao código que a executa. Isso impede tratar operações como dados: não dá para enfileirá-las, agendá-las, registrá-las, repeti-las ou desfazê-las de forma uniforme. Sempre que se quer transformar “uma ação” em algo manipulável, o Command entra.

  • Command: interface com execute() (e opcionalmente undo()).
  • ConcreteCommand: liga uma ação a um Receiver e guarda os parâmetros; implementa execute delegando ao receiver.
  • Receiver: quem realmente sabe fazer o trabalho.
  • Invoker: dispara o comando sem conhecer o receiver nem a ação concreta.
  • Client: cria o ConcreteCommand e configura seu receiver.

Prós

  • Desacopla invoker de receiver.
  • Operações viram objetos de primeira classe: fila, log, agendamento, macro.
  • Undo/redo naturais quando o comando guarda o estado necessário.
  • Fácil compor (macro command) e estender novos comandos sem alterar o invoker (OCP).

Contras

  • Proliferação de classes pequenas.
  • Undo correto pode exigir capturar estado (frequentemente combinado com Memento).
  • Indireção adicional entre pedido e execução.

Para chamadas diretas e simples sem necessidade de fila, log, desfazer ou parametrização. Se não há benefício de “reificar” a ação, uma chamada de método basta.

public class Editor {
private final Document document;
public Editor(Document document) {
this.document = document;
}
public void onKey(char c) {
document.insert(c);
}
}

Sem forma de desfazer, enfileirar ou registrar o que foi digitado.

public interface Command {
void execute();
void undo();
}
public class Document {
private final StringBuilder text = new StringBuilder();
public void insert(String s) { text.append(s); }
public void deleteLast(int n) { text.setLength(Math.max(0, text.length() - n)); }
public String content() { return text.toString(); }
}
public class InsertTextCommand implements Command {
private final Document document;
private final String text;
public InsertTextCommand(Document document, String text) {
this.document = document;
this.text = text;
}
public void execute() {
document.insert(text);
}
public void undo() {
document.deleteLast(text.length());
}
}

Invoker com histórico de undo/redo:

public class CommandInvoker {
private final Deque<Command> undoStack = new ArrayDeque<>();
private final Deque<Command> redoStack = new ArrayDeque<>();
public void run(Command command) {
command.execute();
undoStack.push(command);
redoStack.clear();
}
public void undo() {
if (undoStack.isEmpty()) return;
Command c = undoStack.pop();
c.undo();
redoStack.push(c);
}
public void redo() {
if (redoStack.isEmpty()) return;
Command c = redoStack.pop();
c.execute();
undoStack.push(c);
}
}

Uso:

Document doc = new Document();
CommandInvoker invoker = new CommandInvoker();
invoker.run(new InsertTextCommand(doc, "Ola"));
invoker.run(new InsertTextCommand(doc, " mundo"));
invoker.undo();
System.out.println(doc.content());

O invoker manipula apenas Command; enfileirar, logar ou serializar comandos passa a ser trivial.

sequenceDiagram
    participant Client
    participant Invoker as CommandInvoker
    participant Cmd as InsertTextCommand
    participant Receiver as Document

    Client->>Cmd: new(document, "Ola")
    Client->>Invoker: run(cmd)
    Invoker->>Cmd: execute()
    Cmd->>Receiver: insert("Ola")
    Invoker->>Invoker: undoStack.push(cmd)
    Client->>Invoker: undo()
    Invoker->>Cmd: undo()
    Cmd->>Receiver: deleteLast(3)

É comum confundir o Command (padrão GoF) com o conceito de caso de uso (Use Cases) de arquiteturas limpas/hexagonais. Há semelhança superficial — ambos encapsulam “uma operação” — mas o propósito difere:

  • Command (GoF): mecanismo técnico para tratar uma ação como objeto reificado; foco em undo, fila, log, agendamento, desacoplamento invoker/receiver. É um padrão de implementação.
  • Use Case: unidade de comportamento de aplicação que orquestra regras de domínio, entrada/saída e portas (Ports) na Arquitetura Hexagonal. É um conceito de arquitetura, orientado à intenção de negócio.

Eles podem coexistir: um caso de uso pode ser implementado com objetos de comando (por exemplo, um CommandBus que despacha CreateOrderCommand para um handler). Nesse estilo (CQRS), o “command” carrega a intenção de negócio, mas o padrão que o move pela infraestrutura ainda é o Command do GoF. Não são sinônimos: um é técnica, o outro é fronteira arquitetural.

  • undo() que não restaura o estado real por não ter capturado dados suficientes na execução — combine com Memento quando o inverso não for calculável.
  • Comando anêmico que vira só um wrapper trivial de uma chamada, sem nenhum dos benefícios (fila/log/undo). Se não há ganho, não reifique.
  • Command com lógica de negócio pesada embutida, transformando-o num God Object; ele deve delegar ao receiver.
  • Confundir Command com Use Case e derramar orquestração de aplicação dentro de um padrão de infraestrutura.

1. Que capacidades surgem ao encapsular uma requisição como objeto?

Resposta

Parametrizar clientes com diferentes ações, enfileirar, registrar em log, agendar, compor em macros e principalmente desfazer/refazer — tudo porque a operação passa a ser um dado manipulável, não uma chamada fixa.

2. Qual o papel do Invoker e por que ele não conhece o Receiver?

Resposta

O Invoker apenas dispara execute() (e gerencia histórico para undo/redo) sobre a abstração Command. Ele não conhece o Receiver porque cada ConcreteCommand encapsula a ligação com seu receiver, desacoplando quem pede de quem executa.

3. O que o Command precisa para suportar undo confiável quando a operação não é reversível por cálculo?

Resposta

Precisa capturar o estado necessário para restaurar a situação anterior. Quando o inverso não é calculável (ex.: sobrescrita de dados), combina-se o Command com o Memento, que guarda um snapshot do estado a ser restaurado no undo().

4. Command (GoF) e Use Case são a mesma coisa?

Resposta

Não. Command é um padrão técnico que reifica uma ação (foco em undo/fila/log/desacoplamento). Use Case é uma unidade arquitetural que orquestra regras de negócio e portas. Podem coexistir — um caso de uso pode ser implementado via CommandBus — mas operam em níveis diferentes: técnica vs. fronteira de aplicação.