Menu
Coddy logo textTech

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 clientes con diferentes solicitudes, encolar operaciones o soportar la funcionalidad de deshacer. Desacopla el objeto que invoca una operación de aquel que sabe cómo realizarla.

El patrón tiene cuatro componentes clave: una interfaz Command con un método execute(), Concrete Commands que implementan acciones específicas, un Receiver que realiza el trabajo real y un Invoker 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 de comando
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 la función de deshacer llamando al método undo() de cada comando.

Usa Command cuando necesites encolar operaciones, implementar deshacer/rehacer o desacoplar el emisor de una solicitud de su manejador.

challenge icon

Desafío

Fácil

Vamos a construir un Editor de Texto con funcionalidad de deshacer utilizando el patrón Command. Crearás un sistema donde las operaciones de edición (como insertar y eliminar texto) se encapsulan como objetos de comando, permitiendo a los usuarios ejecutar acciones y luego deshacerlas en orden inverso, tal como 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 TextDocument debe almacenar el contenido del documento como un string. Implementa estos métodos:

    • insertText(const std::string& text) — añade texto al final del contenido
    • deleteText(int count) — elimina los últimos count caracteres del contenido (no hace nada si count excede la longitud del contenido)
    • getContent() — devuelve el contenido actual como una referencia constante
  • Command.h: Define la interfaz de comando y las clases de comando concretas.

    Crea una clase abstracta Command con métodos virtuales puros execute() y undo(), además de un destructor virtual.

    Luego implementa dos comandos concretos:

    • InsertCommand — toma una referencia a un TextDocument y un string para insertar. Cuando se ejecuta, inserta el texto. Cuando se deshace, elimina el mismo número de caracteres que fueron insertados.
    • DeleteCommand — toma una referencia a un TextDocument y un recuento de caracteres a eliminar. Necesita almacenar el texto eliminado para poder restaurarlo al deshacer. Cuando se ejecuta, elimina caracteres del final. Cuando se deshace, inserta el texto almacenado de nuevo.
  • TextEditor.h: Crea el invocador que gestiona la ejecución de comandos y el historial.

    Tu clase TextEditor debe mantener una referencia a un TextDocument y mantener un historial de comandos ejecutados usando un vector de unique_ptr<Command>. Implementa:

    • executeCommand(std::unique_ptr<Command> cmd) — ejecuta el comando y lo añade al historial
    • undo() — deshace el comando más reciente y lo elimina del historial (no hace nada si el historial está vacío)
    • showContent() — imprime 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:

    1. Primer texto a insertar (string)
    2. Segundo texto a insertar (string)
    3. Número de caracteres a eliminar (entero)

    Crea un TextDocument y un TextEditor. Luego realiza estas operaciones en orden, mostrando el contenido después de cada paso:

    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

Por ejemplo, con las entradas Hello, World, y 3:

Hello
Hello World
Hello Wo
Hello World
Hello

Con las entradas Code, Editor, y 6:

Code
CodeEditor
Code
CodeEditor
Code

Observa cómo cada comando sabe cómo revertirse a sí mismo. El DeleteCommand recuerda lo que eliminó para poder restaurarlo, y el InsertCommand sabe exactamente cuántos caracteres eliminar al deshacer. Este es el poder del patrón Command: las operaciones se convierten en objetos de primera clase que pueden ser almacenados, ejecutados y revertidos.

Hoja de referencia

El patrón Command encapsula una solicitud como un objeto, lo que le permite parametrizar clientes con diferentes solicitudes, encolar operaciones 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:

  • Interfaz Command con un método execute()
  • Commands concretos que implementan acciones específicas
  • Receiver (Receptor) que realiza el trabajo real
  • Invoker (Invocador) que activa los comandos

Estructura básica:

// Receiver - sabe cómo realizar las 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;
};

// 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 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, puede implementar la función de deshacer llamando al método undo() de cada comando.

Utilice Command cuando necesite encolar operaciones, implementar deshacer/rehacer o desacoplar al remitente de una solicitud de su manejador.

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: Crear un TextDocument y un TextEditor
    
    // TODO: Realizar las operaciones en orden, mostrando el contenido después de cada una:
    // 1. Insertar el primer texto, luego mostrar el contenido
    // 2. Insertar el segundo texto, luego mostrar el contenido
    // 3. Eliminar el número especificado de caracteres, luego mostrar el contenido
    // 4. Deshacer una vez, luego mostrar el contenido
    // 5. Deshacer una vez más, luego mostrar el contenido
    
    // Hint: Usar std::make_unique<InsertCommand>(...) y 
    //       std::make_unique<DeleteCommand>(...) para crear comandos

    return 0;
}
quiz iconPonte a prueba

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