Patrón Command
Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy. Lección 96 de 104.
El patrón Command encapsula una solicitud como un objeto, lo que permite parametrizar los clientes con distintas solicitudes, poner operaciones en cola o admitir la funcionalidad de deshacer. Desacopla el objeto que invoca una operación del que sabe cómo realizarla.
El patrón tiene cuatro componentes clave: una interfaz Command con un método execute(), comandos concretos que implementan acciones específicas, un receptor que realiza el trabajo real y un invocador que activa los comandos:
#include <iostream>
#include <memory>
#include <vector>
// Receptor - sabe cómo realizar operaciones
class Light {
public:
void turnOn() { std::cout << "Light is ON\n"; }
void turnOff() { std::cout << "Light is OFF\n"; }
};
// Interfaz 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(); }
};
// Invocador
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 almacena una referencia a su receptor y llama al método apropiado cuando se ejecuta. El invocador no sabe qué acción ocurrirá; simplemente llama a execute(). Al almacenar los comandos en un historial, puedes implementar fácilmente el deshacer llamando al método undo() de cada comando.
Usa Command cuando necesites poner operaciones en cola, implementar deshacer/rehacer o desacoplar el emisor de una solicitud de su controlador.
Desafío
FácilConstruyamos un Editor de texto con funcionalidad de deshacer mediante el patrón Command. Crearás un sistema en el que las operaciones de edición (como insertar y eliminar texto) se encapsulan como objetos de comando, lo que permite a los usuarios ejecutar acciones y después deshacerlas en orden inverso, igual que en un editor de texto real.
Organizarás tu código en cuatro archivos:
TextDocument.h: crea la clase receptora que contiene y manipula el contenido de texto real.Tu clase
TextDocumentdebe almacenar el contenido del documento como una cadena. Implementa estos métodos:insertText(const std::string& text): añade el texto al final del contenidodeleteText(int count): elimina los últimoscountcaracteres del contenido (no hace nada si count supera la longitud del contenido)getContent(): devuelve el contenido actual como una referencia const
Command.h: define la interfaz de comando y las clases de comando concretas.Crea una clase abstracta
Commandcon los métodos virtuales purosexecute()yundo(), además de un destructor virtual.Después, implementa dos comandos concretos:
InsertCommand: recibe una referencia a unTextDocumenty una cadena que insertar. Al ejecutarse, inserta el texto. Al deshacerse, elimina el mismo número de caracteres que se insertaron.DeleteCommand: recibe una referencia a unTextDocumenty un número de caracteres que eliminar. Debe almacenar el texto eliminado para poder restaurarlo al deshacer. Al ejecutarse, elimina caracteres del final. Al deshacerse, vuelve a insertar el texto almacenado.
TextEditor.h: crea el invocador que gestiona la ejecución de comandos y el historial.Tu clase
TextEditordebe contener una referencia a unTextDocumenty mantener un historial de los comandos ejecutados mediante un vector deunique_ptr<Command>. Implementa:executeCommand(std::unique_ptr<Command> cmd): ejecuta el comando y lo añade al historialundo(): deshace el comando más reciente y lo elimina del historial (no hace nada si el historial está vacío)showContent(): muestra el contenido actual del documento, o[empty]si el contenido está vacío
main.cpp: demuestra el patrón Command con funcionalidad de deshacer.Lee tres entradas:
- Primer texto que insertar (cadena)
- Segundo texto que insertar (cadena)
- Número de caracteres que eliminar (entero)
Crea un
TextDocumenty unTextEditor. Después, realiza estas operaciones en orden, mostrando el contenido después de cada paso:- Inserta el primer texto y después muestra el contenido
- Inserta el segundo texto y después muestra el contenido
- Elimina el número especificado de caracteres y después muestra el contenido
- Deshaz una vez y después muestra el contenido
- Deshaz una vez más y después muestra el contenido
Por ejemplo, con las entradas Hello, World y 3:
Hello
Hello World
Hello Wo
Hello World
HelloCon las entradas Code, Editor y 6:
Code
CodeEditor
Code
CodeEditor
CodeObserva cómo cada comando sabe cómo revertirse. DeleteCommand recuerda lo que eliminó para poder restaurarlo, y InsertCommand sabe exactamente cuántos caracteres debe eliminar al deshacer. Este es el poder del patrón Command: las operaciones se convierten en objetos de primera clase que se pueden almacenar, ejecutar y revertir.
Pruébalo tú mismo
#include <iostream>
#include <string>
#include <memory>
#include "TextDocument.h"
#include "Command.h"
#include "TextEditor.h"
int main() {
// Leer entradas
std::string firstText;
std::string secondText;
int deleteCount;
std::getline(std::cin, firstText);
std::getline(std::cin, secondText);
std::cin >> deleteCount;
// TODO: Crea un TextDocument y un TextEditor
// TODO: Realiza las operaciones en orden, mostrando el contenido después de cada una:
// 1. Inserta el primer texto, luego muestra el contenido
// 2. Inserta el segundo texto, luego muestra el contenido
// 3. Elimina el número especificado de caracteres, luego muestra el contenido
// 4. Deshaz una vez, luego muestra el contenido
// 5. Deshaz una vez más, luego muestra el contenido
// Pista: Usa std::make_unique<InsertCommand>(...) y
// std::make_unique<DeleteCommand>(...) para crear comandos
return 0;
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de OOP
Archivos externosConstrucción y compilación en C++Archivos de cabecera y archivos fuenteNamespaces y alcanceIntroducción a OOP en C++Clases vs ObjetosEl puntero 'this'Métodos (Funciones miembro)Atributos (Miembros de datos)Conceptos básicos de Ctors y DtorsResumen - Calculadora simple4Propiedades de clase
Miembros de instancia vs. estáticosGetters y SettersFunciones miembro constPalabra clave mutableMétodos y variables estáticosFunciones y clases amigasResumen - Gestor de cuentas bancarias7Herencia
Herencia básicaNiveles de acceso en la herenciaOrden de llamada de Ctor y DtorSobrescritura de métodosFunciones virtuales y VTableHerencia múltipleHerencia virtualResumen - Jerarquía de empleados2Gestión de memoria
Memoria Stack vs HeapPunteros y referenciasMemoria dinámica (new/delete)Punteros inteligentes en C++RAII en C++Resumen - Gestor de arrays dinámicos5Encapsulamiento
Especificadores de acceso en C++Especificadores de acceso en profundidadOcultamiento de informaciónStruct vs ClassClases anidadas e internasResumen - Sistema de registros de estudiantes8Polimorfismo
Polimorfismo: Compilación vs. Tiempo de ejecuciónSobrecarga de funcionesFunciones virtuales revisadasFunciones virtuales purasClases abstractasDiseño de interfaces en C++Dynamic Casting y RTTIResumen: Calculadora de figuras11Conceptos avanzados de POO
Composición vs. HerenciaMixins mediante CRTPIdioma PimplBorrado de tiposEnum Classes y tipado fuerteManejo de excepciones en POOJerarquías de excepciones personalizadas14Patrones de diseño - Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositeRAII como patrón3Constructores y Destructores
Constructor por defectoConstructor parametrizadoConstructor de copiaConstructor de movimientoListas de inicialización del constructorConstructores delegadosAnálisis profundo del destructorRegla de tres / cinco / ceroResumen - Clase String6Sobrecarga de operadores
Introducción a la sobrecarga de operadoresSobrecarga de operadores aritméticosSobrecarga de operadores de comparaciónOperadores de flujo (Stream)Sobrecarga del operador de asignaciónSobrecarga de los operadores [] y ()Operadores de conversión de tiposResumen - Clase Matrix9Plantillas
Plantillas de funcionesPlantillas de clasesEspecialización de plantillasPlantillas variádicasConceptos básicos de SFINAE y Type TraitsResumen - Contenedor genéricoPractica por tu cuenta: Compilador de C++ online