Menu
Coddy logo textTech

Pattern Command

Fa parte della sezione Programmazione orientata agli oggetti del percorso C++ di Coddy. Lezione 96 di 104.

Il pattern Command incapsula una richiesta come oggetto, consentendoti di parametrizzare i client con richieste diverse, mettere in coda le operazioni o supportare la funzionalità di annullamento. Disaccoppia l'oggetto che invoca un'operazione da quello che sa come eseguirla.

Il pattern ha quattro componenti chiave: un'interfaccia Command con un metodo execute(), Comandi concreti che implementano azioni specifiche, un Ricevitore che esegue il lavoro effettivo e un Invocatore che attiva i comandi:

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

// Ricevitore - sa come eseguire le operazioni
class Light {
public:
    void turnOn() { std::cout << "Light is ON\n"; }
    void turnOff() { std::cout << "Light is OFF\n"; }
};

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

// Comandi concreti
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(); }
};

// Invocatore
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();
        }
    }
};

Ogni comando memorizza un riferimento al proprio destinatario e chiama il metodo appropriato quando viene eseguito. L’invocatore non sa quale azione verrà eseguita: si limita a chiamare execute(). Memorizzando i comandi in una cronologia, puoi implementare facilmente l’annullamento chiamando il metodo undo() di ciascun comando.

Usa Command quando devi mettere in coda operazioni, implementare annulla/ripeti o separare il mittente di una richiesta dal relativo gestore.

challenge icon

Sfida

Facile

Costruiamo un Editor di testo con funzionalità di annullamento usando il pattern Command. Creerai un sistema in cui le operazioni di modifica (come l'inserimento e l'eliminazione di testo) sono incapsulate in oggetti comando, consentendo agli utenti di eseguire azioni e poi annullarle in ordine inverso, proprio come in un vero editor di testo.

Organizzerai il codice in quattro file:

  • TextDocument.h: crea la classe ricevente che contiene e manipola il contenuto effettivo del testo.

    La classe TextDocument dovrebbe memorizzare il contenuto del documento come stringa. Implementa questi metodi:

    • insertText(const std::string& text): aggiunge testo alla fine del contenuto
    • deleteText(int count): rimuove gli ultimi count caratteri dal contenuto (non fare nulla se count supera la lunghezza del contenuto)
    • getContent(): restituisce il contenuto corrente come riferimento const
  • Command.h: definisci l'interfaccia del comando e le classi di comando concrete.

    Crea una classe astratta Command con i metodi virtuali puri execute() e undo(), oltre a un distruttore virtuale.

    Implementa quindi due comandi concreti:

    • InsertCommand: accetta un riferimento a un TextDocument e una stringa da inserire. Quando viene eseguito, inserisce il testo. Quando viene annullato, elimina lo stesso numero di caratteri che sono stati inseriti.
    • DeleteCommand: accetta un riferimento a un TextDocument e il numero di caratteri da eliminare. Deve memorizzare il testo eliminato, in modo da poterlo ripristinare quando l'operazione viene annullata. Quando viene eseguito, rimuove i caratteri dalla fine. Quando viene annullato, reinserisce il testo memorizzato.
  • TextEditor.h: crea l'invocatore che gestisce l'esecuzione dei comandi e la cronologia.

    La classe TextEditor dovrebbe mantenere un riferimento a un TextDocument e una cronologia dei comandi eseguiti, usando un vector di unique_ptr<Command>. Implementa:

    • executeCommand(std::unique_ptr<Command> cmd): esegue il comando e lo aggiunge alla cronologia
    • undo(): annulla il comando più recente e lo rimuove dalla cronologia (non fare nulla se la cronologia è vuota)
    • showContent(): stampa il contenuto corrente del documento oppure [empty] se il contenuto è vuoto
  • main.cpp: mostra il pattern Command con funzionalità di annullamento.

    Leggi tre input:

    1. Primo testo da inserire (stringa)
    2. Secondo testo da inserire (stringa)
    3. Numero di caratteri da eliminare (intero)

    Crea un TextDocument e un TextEditor. Esegui quindi queste operazioni nell'ordine indicato, mostrando il contenuto dopo ogni passaggio:

    1. Inserisci il primo testo, poi mostra il contenuto
    2. Inserisci il secondo testo, poi mostra il contenuto
    3. Elimina il numero di caratteri specificato, poi mostra il contenuto
    4. Annulla una volta, poi mostra il contenuto
    5. Annulla ancora una volta, poi mostra il contenuto

Per esempio, con gli input Hello, World e 3:

Hello
Hello World
Hello Wo
Hello World
Hello

Con gli input Code, Editor e 6:

Code
CodeEditor
Code
CodeEditor
Code

Nota come ogni comando sappia come annullare la propria operazione. DeleteCommand ricorda ciò che ha eliminato, così può ripristinarlo, e InsertCommand sa esattamente quanti caratteri rimuovere quando l'operazione viene annullata. Questa è la potenza del pattern Command: le operazioni diventano oggetti di prima classe che possono essere memorizzati, eseguiti e annullati.

Provalo tu

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

int main() {
    // Leggi gli input
    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 e un TextEditor
    
    // TODO: Esegui le operazioni in ordine, mostrando il contenuto dopo ciascuna:
    // 1. Inserisci il primo testo, poi mostra il contenuto
    // 2. Inserisci il secondo testo, poi mostra il contenuto
    // 3. Elimina il numero specificato di caratteri, poi mostra il contenuto
    // 4. Annulla una volta, poi mostra il contenuto
    // 5. Annulla un'altra volta, poi mostra il contenuto
    
    // Suggerimento: usa std::make_unique<InsertCommand>(...) e 
    //       std::make_unique<DeleteCommand>(...) per creare i comandi

    return 0;
}
quiz iconMettiti alla prova

Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.

Tutte le lezioni di Programmazione orientata agli oggetti

Esercitati da solo: Compilatore C++ online