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.
Desafio
FácilVamos 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
TextDocumentdeve 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údodeleteText(int count)— remove os últimoscountcaracteres 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
Commandcom métodos virtuais purosexecute()eundo(), além de um destrutor virtual.Em seguida, implemente dois comandos concretos:
InsertCommand— recebe uma referência para umTextDocumente 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 umTextDocumente 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
TextEditordeve manter uma referência para umTextDocumente manter um histórico de comandos executados usando um vetor deunique_ptr<Command>. Implemente:executeCommand(std::unique_ptr<Command> cmd)— executa o comando e o adiciona ao históricoundo()— 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:
- Primeiro texto a inserir (string)
- Segundo texto a inserir (string)
- Número de caracteres a excluir (inteiro)
Crie um
TextDocumente umTextEditor. Em seguida, realize estas operações em ordem, mostrando o conteúdo após cada etapa:- Insira o primeiro texto e mostre o conteúdo
- Insira o segundo texto e mostre o conteúdo
- Exclua o número especificado de caracteres e mostre o conteúdo
- Desfaça uma vez e mostre o conteúdo
- 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
HelloCom as entradas Code, Editor, e 6:
Code
CodeEditor
Code
CodeEditor
CodeObserve 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;
}
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
1Fundamentos de OOP
Arquivos ExternosBuild e Compilação em C++Arquivos Header e SourceNamespaces e EscopoIntrodução a OOP em C++Classes vs ObjetosO Ponteiro 'this'Métodos (Funções Membro)Atributos (Membros de Dados)Básico de Ctors e DtorsRecapitulação - Calculadora Simples4Propriedades de Classe
Membros de Instância vs EstáticosGetters e SettersFunções de Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRecapitulação - Gerenciador de Conta Bancária7Herança
Herança BásicaNíveis de Acesso na HerançaOrdem de Chamada de Ctor e DtorSobrescrita de MétodosFunções Virtuais e VTableHerança MúltiplaHerança VirtualRecapitulação - Hierarquia de Funcionários2Gerenciamento de Memória
Memória Stack vs HeapPonteiros e ReferênciasMemória Dinâmica (new/delete)Smart Pointers em C++RAII em C++Recapitulação - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de Acesso em C++Especificadores de Acesso em DetalhesOcultação de InformaçãoStruct vs ClassClasses Aninhadas e InternasRecapitulação - Sistema de Registros de Alunos8Polimorfismo
Polimorfismo de Compilação vs Tempo de ExecuçãoSobrecarga de FunçõesFunções Virtuais RevisitadasFunções Virtuais PurasClasses AbstratasDesign de Interfaces em C++Dynamic Casting e RTTIRecapitulação - Calculadora de Formas11Conceitos Avançados de POO
Composição vs HerançaMixins via CRTPPimpl IdiomType ErasureEnum Classes e Tipagem ForteTratamento de Exceções em POOHierarquias de Exceções Personalizadas14Padrões de Projeto Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão CompositeRAII como um Padrão3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentaçãoListas de Inicialização do ConstrutorConstrutores DelegadosMergulho Profundo em DestrutoresRegra dos Três / Cinco / ZeroRecapitulação - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de StreamSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçãoTemplates de ClasseEspecialização de TemplatesTemplates VariádicosFundamentos de SFINAE e Type TraitsRecapitulação - Container Genérico