Command
Encapsula 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.
Intenção
Transformar uma requisição em um objeto independente que contém todas as informações sobre a requisição. Essa transformação permite passar requisições como argumentos de método, atrasar ou enfileirar a execução, e suportar funcionalidade de desfazer/refazer.
Problema
Você precisa emitir requisições a objetos sem saber nada sobre a operação sendo requisitada ou o receptor da requisição. Você também quer suportar desfazer, refazer, log de transações, ou gravação de macro, mas embutir toda essa lógica diretamente no chamador torna o código complexo e fortemente acoplado.
Solução
Crie uma interface Command com um método execute() (e opcionalmente undo()). Cada comando concreto encapsula um receptor e os parâmetros necessários para executar a ação. Um Invoker armazena e dispara comandos sem saber o que eles fazem. Como comandos são objetos, eles podem ser armazenados em uma pilha de histórico para desfazer/refazer, serializados para log, ou compostos em macros.
Participantes
- Command — declara a interface para executar uma operação (e opcionalmente desfazê-la)
- ConcreteCommand — vincula um receptor a uma ação; implementa execute() invocando o método correspondente no receptor
- Invoker — pede ao comando que execute a requisição; mantém o histórico de comandos para desfazer/refazer
- Receiver — sabe como executar as operações associadas a realizar uma requisição
- Client — cria um ConcreteCommand e define seu receptor
Vantagens
- Desacopla o objeto que invoca a operação daquele que sabe como executá-la
- Comandos são objetos de primeira classe que podem ser manipulados, compostos, e estendidos
- Fácil adicionar desfazer/refazer armazenando comandos executados em uma pilha de histórico
- Comandos podem ser reunidos em comandos compostos (macros)
- Suporta execução adiada, enfileiramento, e log de transações
Desvantagens
- Aumenta o número de classes, já que cada ação vira sua própria classe de comando
- Pode ser excessivo para operações simples que não precisam de desfazer ou fila
- A implementação de desfazer pode ser complexa se os comandos têm efeitos colaterais em sistemas externos
Analogia do mundo real
Pedir em um restaurante funciona como o padrão Command. Você (Client) diz ao garçom (Invoker) o que quer. O garçom anota em uma comanda (Command) e a passa para a cozinha (Receiver). A cozinha não precisa interagir com você diretamente, e o garçom não precisa saber cozinhar. Se você mudar de ideia, o garçom pode riscar o item (desfazer). Pedidos também podem ser enfileirados e processados em sequência.
Casos de uso
- Operações de editor de texto — digitar, apagar, negrito, com histórico completo de desfazer/refazer
- Ações de botão de GUI — cada botão dispara um objeto de comando que pode ser reatribuído
- Sistemas de transação — comandos representam operações de banco de dados que podem ser revertidas
- Dispositivos de controle remoto — cada botão mapeia para um comando que controla um dispositivo diferente
- Filas de trabalho e agendadores de tarefas — comandos são serializados e executados de forma assíncrona
- Gravação de macro — uma sequência de comandos é gravada e reproduzida depois
Exemplos de código
import java.util.ArrayDeque;
import java.util.Deque;
interface Command {
void execute();
void undo();
}
final class TextDocument {
private final StringBuilder content = new StringBuilder();
void insert(String text, int position) {
content.insert(position, text);
}
String delete(int position, int length) {
String deleted = content.substring(position, position + length);
content.delete(position, position + length);
return deleted;
}
@Override
public String toString() {
return content.toString();
}
}
final class InsertCommand implements Command {
private final TextDocument doc;
private final String text;
private final int position;
InsertCommand(TextDocument doc, String text, int position) {
this.doc = doc;
this.text = text;
this.position = position;
}
@Override
public void execute() {
doc.insert(text, position);
}
@Override
public void undo() {
doc.delete(position, text.length());
}
}
final class DeleteCommand implements Command {
private final TextDocument doc;
private final int position;
private final int length;
private String deletedText;
DeleteCommand(TextDocument doc, int position, int length) {
this.doc = doc;
this.position = position;
this.length = length;
}
@Override
public void execute() {
deletedText = doc.delete(position, length);
}
@Override
public void undo() {
doc.insert(deletedText, position);
}
}
final class Editor {
private final TextDocument doc = new TextDocument();
private final Deque<Command> history = new ArrayDeque<>();
void runCommand(Command command) {
command.execute();
history.push(command);
}
void undoLast() {
if (!history.isEmpty()) {
history.pop().undo();
}
}
TextDocument document() {
return doc;
}
}
public class CommandDemo {
public static void main(String[] args) {
Editor editor = new Editor();
editor.runCommand(new InsertCommand(editor.document(), "Hello", 0));
editor.runCommand(new InsertCommand(editor.document(), " World", 5));
System.out.println(editor.document()); // Hello World
editor.undoLast();
System.out.println(editor.document()); // Hello
}
}Um editor de texto com suporte a desfazer. InsertCommand e DeleteCommand operam sobre um receptor TextDocument, enquanto o invoker Editor mantém uma pilha de histórico.
Padrões relacionados
Observer
Define uma dependência um-para-muitos entre objetos, de forma que quando um objeto muda de estado, todos os seus dependentes são notificados e atualizados automaticamente.