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.
Défi
FacileConstruisons 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
TextDocumentdoit 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 contenudeleteText(int count)— supprime lescountderniers 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
Commandavec les méthodes virtuelles puresexecute()etundo(), ainsi qu'un destructeur virtuel.Ensuite, implémentez deux commandes concrètes :
InsertCommand— prend une référence vers unTextDocumentet 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 unTextDocumentet 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
TextEditordoit détenir une référence vers unTextDocumentet maintenir un historique des commandes exécutées à l'aide d'un vecteur deunique_ptr<Command>. Implémentez :executeCommand(std::unique_ptr<Command> cmd)— exécute la commande et l'ajoute à l'historiqueundo()— 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 :
- Premier texte à insérer (chaîne)
- Deuxième texte à insérer (chaîne)
- Nombre de caractères à supprimer (entier)
Créez un
TextDocumentet unTextEditor. Effectuez ensuite ces opérations dans l'ordre, en affichant le contenu après chaque étape :- Insérer le premier texte, puis afficher le contenu
- Insérer le deuxième texte, puis afficher le contenu
- Supprimer le nombre de caractères spécifié, puis afficher le contenu
- Annuler une fois, puis afficher le contenu
- 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
HelloAvec les entrées Code, Editor, et 6 :
Code
CodeEditor
Code
CodeEditor
CodeRemarquez 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;
}
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
1Fondamentaux de la POO
Fichiers externesBuild et Compilation C++Fichiers d'en-tête et Fichiers sourceEspaces de noms et PortéeIntroduction à la POO en C++Classes vs ObjetsLe pointeur 'this'Méthodes (Fonctions membres)Attributs (Membres de données)Bases des Ctors et DtorsRécapitulatif - Calculatrice simple4Propriétés de classe
Membres d'instance vs membres statiquesGetters et SettersFonctions membres constMot-clé mutableMéthodes et variables statiquesFonctions et classes amiesRécapitulatif - Gestionnaire de compte bancaire7Héritage
Héritage de baseNiveaux d'accès à l'héritageOrdre d'appel des Ctor et DtorRedéfinition de méthodesFonctions virtuelles et VTableHéritage multipleHéritage virtuelRécapitulatif - Hiérarchie des employés10Présentation de la STL
Présentation et philosophie de la STLConteneurs de la STLItérateursAlgorithmes de la STLFoncteurs et expressions lambdaRécapitulatif - Fréquence des mots13Patrons de conception, Partie 1
Introduction aux patrons de conceptionPatron SingletonFabrique & Fabrique abstraitePatron MonteurPatron ObservateurPatron Stratégie2Gestion de la mémoire
Mémoire Pile vs TasPointeurs et RéférencesMémoire dynamique (new/delete)Smart Pointers en C++RAII en C++Récapitulatif - Gestionnaire de tableaux dynamiques5Encapsulation
Spécificateurs d'accès en C++Spécificateurs d'accès en profondeurMasquage d'informationStruct vs ClassClasses imbriquées et internesRécapitulatif - Système de gestion d'étudiants8Polymorphisme
Polymorphisme : Compilation vs ExécutionSurcharge de fonctionsRetour sur les fonctions virtuellesFonctions virtuelles puresClasses abstraitesConception d'interfaces en C++Dynamic Casting & RTTIRécapitulatif - Calculateur de formes11Concepts avancés de la POO
Composition vs HéritageMixins via CRTPIdiome PimplEffacement de typeEnum Classes & Typage fortGestion des exceptions en POOHiérarchies d'exceptions personnalisées14Patrons de conception, Partie 2
Patron CommandePatron AdaptateurPatron DécorateurPatron Template MethodPatron ÉtatPatron CompositeRAII en tant que patron3Constructeurs et Destructeurs
Constructeur par défautConstructeur paramétréConstructeur de copieConstructeur de déplacementListes d'initialisation du constructeurConstructeurs déléguésAnalyse approfondie du destructeurRègle de trois / cinq / zéroRécapitulatif - Classe String6Surcharge d'opérateurs
Introduction à la surcharge d'opérateursSurcharge des opérateurs arithmétiquesSurcharge des opérateurs de comparaisonOpérateurs de fluxSurcharge de l'opérateur d'affectationSurcharge des opérateurs [] et ()Opérateurs de conversion de typeRécapitulatif - Classe Matrix9Templates
Templates de fonctionsTemplates de classesSpécialisation de templatesTemplates variadiquesBases de SFINAE & Type TraitsRécapitulatif - Conteneur générique