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 parametrizar clientes com diferentes solicitações, enfileirar operações ou oferecer suporte à funcionalidade de desfazer. Ele desacopla o objeto que invoca uma operação daquele que sabe como executá-la.
O padrão tem quatro componentes principais: uma interface Command com um método execute(), Comandos Concretos que implementam ações específicas, um Receptor que realiza o trabalho efetivo e um Invocador que aciona os comandos:
#include <iostream>
#include <memory>
#include <vector>
// 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;
};
// 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 acontecerá — ele apenas chama execute(). Ao armazenar os comandos em um histórico, você pode implementar facilmente o desfazer chamando o método undo() de cada comando.
Use Command quando precisar enfileirar operações, implementar desfazer/refazer ou desacoplar o remetente de uma solicitação de seu manipulador.
Desafio
FácilVamos criar um Editor de Texto com funcionalidade de desfazer usando o padrão Command. Você criará um sistema no qual 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, assim como em um editor de texto real.
Você organizará seu código em quatro arquivos:
TextDocument.h: crie a classe receptora que armazena e manipula o conteúdo real do texto.Sua classe
TextDocumentdeve armazenar o conteúdo do documento como uma string. Implemente estes métodos:insertText(const std::string& text): adiciona o texto ao final do conteúdodeleteText(int count): remove os últimoscountcaracteres do conteúdo (não faz nada se count exceder o tamanho do conteúdo)getContent(): retorna o conteúdo atual como uma referência const
Command.h: defina a interface de comando e as classes de comando concretas.Crie uma classe abstrata
Commandcom os métodos virtuais purosexecute()eundo(), além de um destrutor virtual.Em seguida, implemente dois comandos concretos:
InsertCommand: recebe uma referência a umTextDocumente uma string para inserir. Quando executado, insere o texto. Quando desfeito, exclui a mesma quantidade de caracteres que foi inserida.DeleteCommand: recebe uma referência a umTextDocumente uma quantidade de caracteres a excluir. Ele precisa armazenar o texto excluído para poder restaurá-lo ao desfazer. Quando executado, remove os caracteres do final. Quando desfeito, insere novamente o texto armazenado.
TextEditor.h: crie o invocador que gerencia a execução e o histórico dos comandos.Sua classe
TextEditordeve manter uma referência a umTextDocumente manter um histórico dos 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 faz nada se o histórico estiver vazio)showContent(): exibe 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 na ordem indicada, exibindo o conteúdo após cada etapa:- Insira o primeiro texto e exiba o conteúdo
- Insira o segundo texto e exiba o conteúdo
- Exclua a quantidade especificada de caracteres e exiba o conteúdo
- Desfaça uma vez e exiba o conteúdo
- Desfaça mais uma vez e exiba 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. Esse é o poder do padrão Command: as operações se tornam objetos de primeira classe que podem ser armazenados, executados e revertidos.
Experimente você mesmo
#include <iostream>
#include <string>
#include <memory>
#include "TextDocument.h"
#include "Command.h"
#include "TextEditor.h"
int main() {
// Ler entradas
std::string firstText;
std::string secondText;
int deleteCount;
std::getline(std::cin, firstText);
std::getline(std::cin, secondText);
std::cin >> deleteCount;
// TODO: Crie um TextDocument e um TextEditor
// TODO: Execute as operações em ordem, mostrando o conteúdo após cada uma:
// 1. Insira o primeiro texto, depois mostre o conteúdo
// 2. Insira o segundo texto, depois mostre o conteúdo
// 3. Delete o número especificado de caracteres, depois mostre o conteúdo
// 4. Desfaça uma vez, depois mostre o conteúdo
// 5. Desfaça mais uma vez, depois mostre 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 POO
Arquivos externosBuild e compilação em C++Arquivos de cabeçalho e arquivos-fonteNamespaces e escopoIntrodução à POO em C++Classes vs. objetosO ponteiro 'this'Métodos (funções membro)Atributos (membros de dados)Noções básicas de construtores e destruidoresRevisão - calculadora simples4Propriedades de Classe
Membros de Instância vs. EstáticosGetters e SettersFunções Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRevisão - Gerenciador de Contas Bancárias7Herança
Herança básicaNíveis de acesso da herançaOrdem de chamada de construtor e destrutorSobrescrita de métodosFunções virtuais e VTableHerança múltiplaHerança virtualRevisão - Hierarquia de funcionários10Visão geral da STL
Visão geral e filosofia da STLContêineres da STLIteradoresAlgoritmos da STLFunctors e expressões lambdaRecapitulação - Frequência de palavras13Padrões de Projeto — Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonFactory e Abstract FactoryPadrão BuilderPadrão ObserverPadrão Strategy2Gerenciamento de Memória
Memória Stack vs. HeapPonteiros e ReferênciasMemória Dinâmica (new/delete)Ponteiros Inteligentes em C++RAII em C++Revisão - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de acesso em C++Especificadores de acesso em profundidadeOcultação de informaçõesStruct vs ClasseClasses aninhadas e internasRecapitulação - Sistema de registros de alunos8Polimorfismo
Polimorfismo em compilação vs. execuçãoSobrecarga de funçõesFunções virtuais revisadasFunções virtuais purasClasses abstratasDesign de interfaces em C++Conversão dinâmica e RTTIRecapitulação — Calculadora de formas11Conceitos Avançados de OOP
Composição vs. HerançaMixins via CRTPIdioma PimplApagamento de TiposClasses Enum e Tipagem ForteTratamento de Exceções em OOPHierarquias de Exceções Personalizadas14Padrões de Projeto — Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão CompositeRAII como Padrão3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentoListas de Inicialização do ConstrutorConstrutores DelegantesAprofundamento em DestrutoresRegra dos Três / Cinco / ZeroRevisão - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de FluxoSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçõesTemplates de ClassesEspecialização de TemplatesTemplates VariádicosNoções Básicas de SFINAE e Traits de TiposRecapitulação - Contêiner GenéricoPratique por conta própria: Compilador de C++ online