Menu
Coddy logo textTech

Pattern Commande

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

Le pattern Command encapsule une requête sous forme d’objet, ce qui vous permet de paramétrer les 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’effectuer.

Le modèle comporte quatre composants clés : une interface Command dotée d’une méthode execute(), des commandes concrètes qui implémentent des actions spécifiques, un récepteur qui effectue le travail réel et un invocateur qui déclenche les commandes :

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

// Récepteur - 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 Commande
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(); }
};

// Invocateur
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’invocateur 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 Command lorsque vous devez mettre des opérations en file d’attente, implémenter l’annulation et le rétablissement, ou découpler l’émetteur d’une requête de son gestionnaire.

challenge icon

Défi

Facile

Construisons un éditeur de texte doté d'une fonctionnalité d'annulation à l'aide du pattern Command. Vous allez créer un système dans lequel les opérations d'édition (comme l'insertion et la suppression de texte) sont encapsulées sous forme d'objets commande, ce qui permet aux utilisateurs d'exécuter des actions, puis de les annuler dans l'ordre inverse, comme dans un véritable éditeur de texte.

Vous allez organiser votre code dans quatre fichiers :

  • TextDocument.h : créez la classe réceptrice qui contient et manipule le contenu réel du texte.

    Votre classe TextDocument doit stocker le contenu du document sous forme de chaîne. Implémentez ces méthodes :

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

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

    Implémentez ensuite deux commandes concrètes :

    • InsertCommand : prend une référence vers un TextDocument et une chaîne à insérer. Lors de son exécution, elle insère le texte. Lors de son annulation, elle supprime le même nombre de caractères que celui qui a été inséré.
    • 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. Lors de son exécution, elle supprime les caractères à la fin. Lors de son annulation, elle réinsère le texte stocké.
  • TextEditor.h : créez l'invocateur qui gère l'exécution des commandes et l'historique.

    Votre classe TextEditor doit contenir une référence vers un TextDocument et conserver 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 supprime de l'historique (ne fait rien si l'historique est vide)
    • showContent() : affiche le contenu actuel du document, ou [empty] si le contenu est vide
  • main.cpp : démontrez le pattern 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 indiqué, 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 s'annuler elle-même. DeleteCommand mémorise ce qu'elle a supprimé afin de pouvoir le restaurer, et InsertCommand sait exactement combien de caractères supprimer lors de l'annulation. C'est là toute la puissance du pattern Command : les opérations deviennent des objets de première classe qui peuvent être stockés, exécutés et inversés.

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 spécifié de caractères, puis afficher le contenu
    // 4. Annuler une fois, puis afficher le contenu
    // 5. Annuler une fois de plus, puis afficher le contenu
    
    // Astuce : Utilisez std::make_unique<InsertCommand>(...) et 
    //       std::make_unique<DeleteCommand>(...) pour créer les 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

Entraînez-vous par vous-même : Compilateur C++ en ligne