Menu
Coddy logo textTech

Padrão Command

Parte da seção Programação Orientada a Objetos do Journey de C++ da Coddy — lição 96 de 104.

O padrão Command encapsula uma solicitação como um objeto, permitindo que você parametrize clientes com diferentes solicitações, enfileire operações ou suporte a funcionalidade de desfazer. Ele desacopla o objeto que invoca uma operação daquele que sabe como executá-la.

O padrão possui quatro componentes principais: uma interface Command com um método execute(), Concrete Commands que implementam ações específicas, um Receiver que realiza o trabalho real e um Invoker que aciona os comandos:

#include <iostream>
#include <memory>
#include <vector>

// Receiver - sabe como realizar as operações
class Light {
public:
    void turnOn() { std::cout << "Light is ON\n"; }
    void turnOff() { std::cout << "Light is OFF\n"; }
};

// Interface Command
class Command {
public:
    virtual void execute() = 0;
    virtual void undo() = 0;
    virtual ~Command() = default;
};

// Comandos Concretos
class LightOnCommand : public Command {
    Light& light;
public:
    LightOnCommand(Light& l) : light(l) {}
    void execute() override { light.turnOn(); }
    void undo() override { light.turnOff(); }
};

class LightOffCommand : public Command {
    Light& light;
public:
    LightOffCommand(Light& l) : light(l) {}
    void execute() override { light.turnOff(); }
    void undo() override { light.turnOn(); }
};

// Invoker
class RemoteControl {
    std::vector<std::unique_ptr<Command>> history;
public:
    void pressButton(std::unique_ptr<Command> cmd) {
        cmd->execute();
        history.push_back(std::move(cmd));
    }
    
    void pressUndo() {
        if (!history.empty()) {
            history.back()->undo();
            history.pop_back();
        }
    }
};

Cada comando armazena uma referência ao seu receptor e chama o método apropriado quando executado. O invocador não sabe qual ação irá acontecer - ele apenas chama execute(). Ao armazenar comandos em um histórico, você pode implementar o desfazer facilmente chamando o método undo() de cada comando.

Use o Command quando você precisar enfileirar operações, implementar desfazer/refazer ou desacoplar o remetente de uma solicitação de seu destinatário.

challenge icon

Desafio

Fácil

Vamos construir um Editor de Texto com funcionalidade de desfazer (undo) usando o padrão Command. Você criará um sistema onde as operações de edição (como inserir e excluir texto) são encapsuladas como objetos de comando, permitindo que os usuários executem ações e depois as desfaçam na ordem inversa — exatamente como em um editor de texto real.

Você organizará seu código em quatro arquivos:

  • TextDocument.h: Crie a classe receiver que mantém e manipula o conteúdo de texto real.

    Sua classe TextDocument deve armazenar o conteúdo do documento como uma string. Implemente estes métodos:

    • insertText(const std::string& text) — anexa o texto ao final do conteúdo
    • deleteText(int count) — remove os últimos count caracteres do conteúdo (não faça nada se count exceder o comprimento do conteúdo)
    • getContent() — retorna o conteúdo atual como uma referência constante
  • Command.h: Defina a interface de comando e as classes de comando concretas.

    Crie uma classe abstrata Command com métodos virtuais puros execute() e undo(), além de um destrutor virtual.

    Em seguida, implemente dois comandos concretos:

    • InsertCommand — recebe uma referência para um TextDocument e uma string para inserir. Quando executado, ele insere o texto. Quando desfeito, ele exclui o mesmo número de caracteres que foram inseridos.
    • DeleteCommand — recebe uma referência para um TextDocument e uma contagem de caracteres para excluir. Ele precisa armazenar o texto excluído para que possa restaurá-lo ao desfazer. Quando executado, ele remove caracteres do final. Quando desfeito, ele insere o texto armazenado de volta.
  • TextEditor.h: Crie o invoker que gerencia a execução de comandos e o histórico.

    Sua classe TextEditor deve manter uma referência para um TextDocument e manter um histórico de comandos executados usando um vetor de unique_ptr<Command>. Implemente:

    • executeCommand(std::unique_ptr<Command> cmd) — executa o comando e o adiciona ao histórico
    • undo() — desfaz o comando mais recente e o remove do histórico (não faça nada se o histórico estiver vazio)
    • showContent() — imprime o conteúdo atual do documento, ou [empty] se o conteúdo estiver vazio
  • main.cpp: Demonstre o padrão Command com funcionalidade de desfazer.

    Leia três entradas:

    1. Primeiro texto a inserir (string)
    2. Segundo texto a inserir (string)
    3. Número de caracteres a excluir (inteiro)

    Crie um TextDocument e um TextEditor. Em seguida, realize estas operações em ordem, mostrando o conteúdo após cada etapa:

    1. Insira o primeiro texto e mostre o conteúdo
    2. Insira o segundo texto e mostre o conteúdo
    3. Exclua o número especificado de caracteres e mostre o conteúdo
    4. Desfaça uma vez e mostre o conteúdo
    5. Desfaça mais uma vez e mostre o conteúdo

Por exemplo, com as entradas Hello, World, e 3:

Hello
Hello World
Hello Wo
Hello World
Hello

Com as entradas Code, Editor, e 6:

Code
CodeEditor
Code
CodeEditor
Code

Observe como cada comando sabe como se reverter. O DeleteCommand lembra o que excluiu para poder restaurá-lo, e o InsertCommand sabe exatamente quantos caracteres remover ao desfazer. Este é o poder do padrão Command — as operações tornam-se objetos de primeira classe que podem ser armazenados, executados e revertidos.

Folha de consulta

O padrão Command encapsula uma solicitação como um objeto, permitindo que você parametrize clientes com diferentes solicitações, enfileire operações ou suporte a funcionalidade de desfazer. Ele desacopla o objeto que invoca uma operação daquele que sabe como executá-la.

O padrão possui quatro componentes principais:

  • Interface Command com um método execute()
  • Commands Concretos que implementam ações específicas
  • Receiver que realiza o trabalho real
  • Invoker que dispara os comandos

Estrutura básica:

// Receiver - sabe como realizar operações
class Light {
public:
    void turnOn() { std::cout << "Light is ON\n"; }
    void turnOff() { std::cout << "Light is OFF\n"; }
};

// Interface Command
class Command {
public:
    virtual void execute() = 0;
    virtual void undo() = 0;
    virtual ~Command() = default;
};

// Command Concreto
class LightOnCommand : public Command {
    Light& light;
public:
    LightOnCommand(Light& l) : light(l) {}
    void execute() override { light.turnOn(); }
    void undo() override { light.turnOff(); }
};

// Invoker
class RemoteControl {
    std::vector<std::unique_ptr<Command>> history;
public:
    void pressButton(std::unique_ptr<Command> cmd) {
        cmd->execute();
        history.push_back(std::move(cmd));
    }
    
    void pressUndo() {
        if (!history.empty()) {
            history.back()->undo();
            history.pop_back();
        }
    }
};

Cada comando armazena uma referência ao seu receptor e chama o método apropriado quando executado. O invoker não sabe qual ação acontecerá — ele apenas chama execute(). Ao armazenar comandos em um histórico, você pode implementar o desfazer chamando o método undo() de cada comando.

Use o Command quando precisar enfileirar operações, implementar desfazer/refazer ou desacoplar o remetente de uma solicitação de seu manipulador.

Experimente você mesmo

#include <iostream>
#include <string>
#include <memory>
#include "TextDocument.h"
#include "Command.h"
#include "TextEditor.h"

int main() {
    // Ler as entradas
    std::string firstText;
    std::string secondText;
    int deleteCount;
    
    std::getline(std::cin, firstText);
    std::getline(std::cin, secondText);
    std::cin >> deleteCount;

    // TODO: Criar um TextDocument e um TextEditor
    
    // TODO: Realizar as operações em ordem, mostrando o conteúdo após cada uma:
    // 1. Inserir o primeiro texto, depois mostrar o conteúdo
    // 2. Inserir o segundo texto, depois mostrar o conteúdo
    // 3. Excluir o número especificado de caracteres, depois mostrar o conteúdo
    // 4. Desfazer uma vez, depois mostrar o conteúdo
    // 5. Desfazer mais uma vez, depois mostrar o conteúdo
    
    // Dica: Use std::make_unique<InsertCommand>(...) e 
    //       std::make_unique<DeleteCommand>(...) para criar comandos

    return 0;
}
quiz iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Programação Orientada a Objetos