Menu
Coddy logo textTech

Patron Commande

Fait partie de la section Programmation Orientée Objet du Journey C++ de Coddy — leçon 96 sur 104.

Le patron Commande encapsule une requête sous la forme d'un objet, vous permettant ainsi de paramétrer des clients avec différentes requêtes, de mettre des opérations en file d'attente ou de prendre en charge la fonctionnalité d'annulation. Il découple l'objet qui invoque une opération de celui qui sait comment l'exécuter.

Le patron comporte quatre composants clés : une interface Command avec une méthode execute(), des Concrete Commands qui implémentent des actions spécifiques, un Receiver qui effectue le travail réel, et un Invoker qui déclenche les commandes :

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

// Receiver - sait comment effectuer les opérations
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;
};

// Commandes concrètes
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();
        }
    }
};

Chaque commande stocke une référence à son récepteur et appelle la méthode appropriée lorsqu'elle est exécutée. L'appelant ne sait pas quelle action va se produire - il appelle simplement execute(). En stockant les commandes dans un historique, vous pouvez facilement implémenter l'annulation en appelant la méthode undo() de chaque commande.

Utilisez le motif Commande lorsque vous devez mettre des opérations en file d'attente, implémenter l'annulation/rétablissement, ou découpler l'émetteur d'une requête de son destinataire.

challenge icon

Défi

Facile

Construisons un Éditeur de Texte avec une fonctionnalité d'annulation (undo) en utilisant le patron de conception Command. Vous allez créer un système où les opérations d'édition (comme l'insertion et la suppression de texte) sont encapsulées sous forme d'objets de commande, permettant aux utilisateurs d'exécuter des actions puis de les annuler dans l'ordre inverse — tout comme un véritable éditeur de texte.

Vous organiserez votre code sur quatre fichiers :

  • TextDocument.h : Créez la classe réceptrice (receiver) qui détient et manipule le contenu textuel réel.

    Votre classe TextDocument doit stocker le contenu du document sous forme de chaîne de caractères (string). Implémentez ces méthodes :

    • insertText(const std::string& text) — ajoute le texte à la fin du contenu
    • deleteText(int count) — supprime les count derniers caractères du contenu (ne rien faire si count dépasse la longueur du contenu)
    • getContent() — retourne le contenu actuel sous forme de référence constante
  • Command.h : Définissez l'interface de commande et les classes de commande concrètes.

    Créez une classe abstraite Command avec les méthodes virtuelles pures execute() et undo(), ainsi qu'un destructeur virtuel.

    Ensuite, implémentez deux commandes concrètes :

    • InsertCommand — prend une référence vers un TextDocument et une chaîne à insérer. Lorsqu'elle est exécutée, elle insère le texte. Lorsqu'elle est annulée, elle supprime le même nombre de caractères que ceux qui ont été insérés.
    • DeleteCommand — prend une référence vers un TextDocument et un nombre de caractères à supprimer. Elle doit stocker le texte supprimé afin de pouvoir le restaurer lors de l'annulation. Lorsqu'elle est exécutée, elle supprime les caractères à la fin. Lorsqu'elle est annulée, elle réinsère le texte stocké.
  • TextEditor.h : Créez l'invocateur (invoker) qui gère l'exécution des commandes et l'historique.

    Votre classe TextEditor doit détenir une référence vers un TextDocument et maintenir un historique des commandes exécutées à l'aide d'un vecteur de unique_ptr<Command>. Implémentez :

    • executeCommand(std::unique_ptr<Command> cmd) — exécute la commande et l'ajoute à l'historique
    • undo() — annule la commande la plus récente et la retire de l'historique (ne rien faire si l'historique est vide)
    • showContent() — affiche le contenu actuel du document, ou [empty] si le contenu est vide
  • main.cpp : Démontrez le patron Command avec la fonctionnalité d'annulation.

    Lisez trois entrées :

    1. Premier texte à insérer (chaîne)
    2. Deuxième texte à insérer (chaîne)
    3. Nombre de caractères à supprimer (entier)

    Créez un TextDocument et un TextEditor. Effectuez ensuite ces opérations dans l'ordre, en affichant le contenu après chaque étape :

    1. Insérer le premier texte, puis afficher le contenu
    2. Insérer le deuxième texte, puis afficher le contenu
    3. Supprimer le nombre de caractères spécifié, puis afficher le contenu
    4. Annuler une fois, puis afficher le contenu
    5. Annuler une fois de plus, puis afficher le contenu

Par exemple, avec les entrées Hello, World, et 3 :

Hello
Hello World
Hello Wo
Hello World
Hello

Avec les entrées Code, Editor, et 6 :

Code
CodeEditor
Code
CodeEditor
Code

Remarquez comment chaque commande sait comment s'inverser. La DeleteCommand se souvient de ce qu'elle a supprimé pour pouvoir le restaurer, et la InsertCommand sait exactement combien de caractères retirer lors de l'annulation. C'est la puissance du patron Command — les opérations deviennent des objets de premier plan qui peuvent être stockés, exécutés et inversés.

Aide-mémoire

Le patron de conception Commande (Command pattern) encapsule une requête sous forme d'objet, ce qui vous permet de paramétrer des clients avec différentes requêtes, de mettre des opérations en file d'attente ou de prendre en charge la fonctionnalité d'annulation (undo). Il découple l'objet qui invoque une opération de celui qui sait comment l'exécuter.

Le patron comporte quatre composants clés :

  • Interface Commande avec une méthode execute()
  • Commandes concrètes qui implémentent des actions spécifiques
  • Récepteur (Receiver) qui effectue le travail réel
  • Invocateur (Invoker) qui déclenche les commandes

Structure de base :

// Receiver - knows how to perform operations
class Light {
public:
    void turnOn() { std::cout << "Light is ON\n"; }
    void turnOff() { std::cout << "Light is OFF\n"; }
};

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

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

Chaque commande stocke une référence à son récepteur et appelle la méthode appropriée lors de son exécution. L'invocateur ne sait pas quelle action va se produire — il appelle simplement execute(). En stockant les commandes dans un historique, vous pouvez implémenter l'annulation en appelant la méthode undo() de chaque commande.

Utilisez le patron Commande lorsque vous devez mettre des opérations en file d'attente, implémenter l'annulation/rétablissement (undo/redo) ou découpler l'émetteur d'une requête de son gestionnaire.

Essayez vous-même

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

int main() {
    // Lire les entrées
    std::string firstText;
    std::string secondText;
    int deleteCount;
    
    std::getline(std::cin, firstText);
    std::getline(std::cin, secondText);
    std::cin >> deleteCount;

    // TODO: Créer un TextDocument et un TextEditor
    
    // TODO: Effectuer les opérations dans l'ordre, en affichant le contenu après chacune :
    // 1. Insérer le premier texte, puis afficher le contenu
    // 2. Insérer le second texte, puis afficher le contenu
    // 3. Supprimer le nombre de caractères spécifié, puis afficher le contenu
    // 4. Annuler une fois, puis afficher le contenu
    // 5. Annuler une fois de plus, puis afficher le contenu
    
    // Astuce : Utiliser std::make_unique<InsertCommand>(...) et 
    //       std::make_unique<DeleteCommand>(...) pour créer des commandes

    return 0;
}
quiz iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Programmation Orientée Objet