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.
Défi
FacileConstruisons 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
TextDocumentdoit 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 contenudeleteText(int count): supprime les derniers caractèrescountdu 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
Commandavec les méthodes virtuelles puresexecute()etundo(), ainsi qu'un destructeur virtuel.Implémentez ensuite deux commandes concrètes :
InsertCommand: prend une référence vers unTextDocumentet 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 unTextDocumentet 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
TextEditordoit contenir une référence vers unTextDocumentet conserver 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 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 :
- 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 indiqué, 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 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;
}
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 externesGénération et compilation en C++Fichiers d’en-tête et fichiers sourceEspaces de noms et portéeIntroduction à la POO en C++Classes et objetsLe pointeur « this »Méthodes (fonctions membres)Attributs (membres de données)Bases des constructeurs et destructeursRécapitulatif - Calculatrice simple4Propriétés de classe
Membres d’instance et statiquesAccesseurs et mutateursFonctions membres constMot-clé mutableMéthodes et variables statiquesFonctions et classes amiesRécapitulatif - Gestionnaire de compte bancaire7Héritage
Héritage de baseNiveaux d’accès de l’héritageOrdre d’appel des constructeurs et destructeursRedéfinition des méthodesFonctions virtuelles et VTableHéritage multipleHéritage virtuelRécapitulatif : hiérarchie des employés10Vue d’ensemble de la STL
Vue d’ensemble et philosophie de la STLConteneurs de la STLItérateursAlgorithmes de la STLFoncteurs et expressions lambdaRécapitulatif - fréquence des mots13Modèles de conception, partie 1
Introduction aux modèles de conceptionModèle SingletonFabrique et fabrique abstraiteModèle BuilderModèle ObserverModèle Strategy2Gestion de la mémoire
Mémoire de pile ou de tasPointeurs et référencesMémoire dynamique (new/delete)Pointeurs intelligents 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 de l’informationStruct vs classeClasses imbriquées et internesRécapitulatif - Système de gestion des dossiers étudiants8Polymorphisme
Polymorphisme à la compilation vs à l’exécutionSurcharge de fonctionsRetour sur les fonctions virtuellesFonctions virtuelles puresClasses abstraitesConception d’interfaces en C++Conversion dynamique et RTTIRécapitulatif - Calculateur de formes11Concepts avancés de la POO
Composition ou héritageMixins via CRTPIdiom PimplEffacement de typeClasses enum et typage fortGestion des exceptions en POOHiérarchies d’exceptions personnalisées14Design Patterns – Partie 2
Pattern CommandePattern AdaptateurPattern DécorateurPattern Méthode ModèlePattern ÉtatPattern CompositeRAII comme Pattern3Constructeurs et destructeurs
Constructeur par défautConstructeur paramétréConstructeur de copieConstructeur de déplacementListes d’initialisation des constructeursConstructeurs déléguésApprofondissement des destructeursRègle des trois / cinq / zéroRécapitulatif - Classe String6Surcharge des opérateurs
Introduction à la surcharge des 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 des templatesTemplates variadiquesBases de SFINAE et des traits de typesRécapitulatif - Conteneur génériqueEntraînez-vous par vous-même : Compilateur C++ en ligne