Pular para o conteúdo
Comportamentais

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

Editor.java
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.