Patron Commande
Fait partie de la section Programmation Orientée Objet du Journey GO de Coddy — leçon 91 sur 107.
Le patron Commande encapsule une requête sous la forme d'un objet, vous permettant ainsi de paramétrer des opérations, de les mettre en file d'attente ou de prendre en charge la fonctionnalité d'annulation. Alors que Strategy encapsule des algorithmes, Commande encapsule des actions entières ainsi que leurs paramètres.
En Go, nous définissons une interface Command avec une méthode Execute, puis nous créons des commandes concrètes qui détiennent toutes les informations nécessaires pour effectuer une action :
type Command interface {
Execute() string
}
type Light struct {
IsOn bool
}
type TurnOnCommand struct {
light *Light
}
func (c *TurnOnCommand) Execute() string {
c.light.IsOn = true
return "Light turned on"
}
type TurnOffCommand struct {
light *Light
}
func (c *TurnOffCommand) Execute() string {
c.light.IsOn = false
return "Light turned off"
}Un invoker stocke et exécute des commandes sans savoir ce qu'elles font :
type RemoteControl struct {
command Command
}
func (r *RemoteControl) SetCommand(c Command) {
r.command = c
}
func (r *RemoteControl) PressButton() string {
return r.command.Execute()
}L'appelant est complètement découplé du récepteur (la Lumière) :
light := &Light{}
remote := &RemoteControl{}
remote.SetCommand(&TurnOnCommand{light: light})
fmt.Println(remote.PressButton()) // Lumière allumée
remote.SetCommand(&TurnOffCommand{light: light})
fmt.Println(remote.PressButton()) // Lumière éteinteLe pattern Command est idéal pour implémenter des systèmes d'annulation/rétablissement, des files d'attente de tâches ou l'enregistrement de macros lorsque vous devez stocker, différer ou rejouer des opérations.
Défi
FacileConstruisons un éditeur de texte avec une fonctionnalité d'annulation (undo) en utilisant le pattern Command ! Vous allez créer des commandes qui encapsulent les opérations de texte, permettant à l'éditeur d'exécuter des actions et de les inverser — un cas d'utilisation classique qui démontre pourquoi les commandes sont si puissantes.
Vous organiserez votre code sur trois fichiers :
command.go: Définissez votre interface de commande et les commandes concrètes d'édition de texte.Créez une interface
Commandavec deux méthodes :Execute() stringpour effectuer l'action etUndo() stringpour l'inverser.Implémentez deux types de commandes qui opèrent sur un
*TextEditor:InsertCommand— contient le pointeur de l'éditeur et le texte à insérer. Execute ajoute le texte au contenu de l'éditeur et retourneInserted: [text]. Undo supprime ce texte de la fin et retourneUndone insert: [text]DeleteCommand— contient le pointeur de l'éditeur et un nombre de caractères à supprimer de la fin. Execute supprime ces caractères (en les stockant pour l'annulation) et retourneDeleted: [removed text]. Undo les restaure et retourneUndone delete: [restored text]
editor.go: Créez votre éditeur de texte (le récepteur) et l'invocateur (invoker) qui gère l'historique des commandes.Construisez une structure
TextEditoravec un champContent(string) et des méthodesAppend(text string)etDeleteLast(count int) string(retourne le texte supprimé).Construisez une structure
EditorInvokerqui contient une tranche (slice) de commandes exécutées comme historique. Ajoutez ces méthodes :ExecuteCommand(cmd Command) string— exécute la commande, l'ajoute à l'historique et retourne le résultatUndoLast() string— supprime la dernière commande de l'historique, appelle son Undo et retourne le résultat. Si l'historique est vide, retournezNothing to undo
main.go: Démontrez votre système de commande avec une série d'opérations.Lisez un nombre d'opérations. Pour chaque opération, lisez le type d'opération (
insert,deleteouundo). Pour insert, lisez également le texte à insérer. Pour delete, lisez le nombre de caractères. Exécutez chaque opération via votre invocateur et affichez le résultat. Après toutes les opérations, affichez le contenu final de l'éditeur sous la formeFinal: [content].
Les entrées suivantes seront fournies :
- Ligne 1 : Nombre d'opérations
- Pour chaque opération : type d'opération, puis données supplémentaires si nécessaire (texte pour insert, nombre pour delete)
Par exemple, étant donné :
5
insert
Hello
insert
World
delete
3
undo
undoVotre sortie devrait être :
Inserted: Hello
Inserted: World
Deleted: rld
Undone delete: rld
Undone insert: World
Final: HelloEt étant donné :
4
insert
Go
insert
Lang
undo
insert
!Votre sortie devrait être :
Inserted: Go
Inserted: Lang
Undone insert: Lang
Inserted: !
Final: Go!Et étant donné :
2
undo
insert
TestVotre sortie devrait être :
Nothing to undo
Inserted: Test
Final: TestRemarquez comment chaque commande encapsule tout ce qui est nécessaire pour effectuer et inverser son action. L'invocateur ne sait pas ce que font les commandes — il se contente de les exécuter et de maintenir l'historique pour le support de l'annulation !
Aide-mémoire
Le pattern Command encapsule une requête sous forme d'objet, vous permettant de paramétrer des opérations, de les mettre en file d'attente ou de prendre en charge la fonctionnalité d'annulation (undo).
Définissez une interface Command avec une méthode Execute :
type Command interface {
Execute() string
}Créez des commandes concrètes qui contiennent toutes les informations nécessaires pour effectuer une action :
type Light struct {
IsOn bool
}
type TurnOnCommand struct {
light *Light
}
func (c *TurnOnCommand) Execute() string {
c.light.IsOn = true
return "Light turned on"
}
type TurnOffCommand struct {
light *Light
}
func (c *TurnOffCommand) Execute() string {
c.light.IsOn = false
return "Light turned off"
}Un invocateur (invoker) stocke et exécute des commandes sans savoir ce qu'elles font :
type RemoteControl struct {
command Command
}
func (r *RemoteControl) SetCommand(c Command) {
r.command = c
}
func (r *RemoteControl) PressButton() string {
return r.command.Execute()
}Exemple d'utilisation montrant le découplage entre l'invocateur et le récepteur :
light := &Light{}
remote := &RemoteControl{}
remote.SetCommand(&TurnOnCommand{light: light})
fmt.Println(remote.PressButton()) // Light turned on
remote.SetCommand(&TurnOffCommand{light: light})
fmt.Println(remote.PressButton()) // Light turned offPour la fonctionnalité d'annulation, ajoutez une méthode Undo à l'interface Command et maintenez un historique des commandes dans l'invocateur.
Essayez vous-même
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Lire le nombre d'opérations
line, _ := reader.ReadString('\n')
numOps, _ := strconv.Atoi(strings.TrimSpace(line))
// Créer l'éditeur et l'invocateur
editor := &TextEditor{}
invoker := &EditorInvoker{}
for i := 0; i < numOps; i++ {
// Lire le type d'opération
opLine, _ := reader.ReadString('\n')
opType := strings.TrimSpace(opLine)
var result string
switch opType {
case "insert":
// Lire le texte à insérer
textLine, _ := reader.ReadString('\n')
text := strings.TrimSuffix(textLine, "\n")
// TODO: Créer une InsertCommand et l'exécuter via l'invocateur
_ = text
_ = editor
case "delete":
// Lire le nombre de caractères à supprimer
countLine, _ := reader.ReadString('\n')
count, _ := strconv.Atoi(strings.TrimSpace(countLine))
// TODO: Créer une DeleteCommand et l'exécuter via l'invocateur
_ = count
case "undo":
// TODO: Appeler UndoLast sur l'invocateur
}
fmt.Println(result)
}
// Afficher le contenu final
fmt.Printf("Final: %s\n", editor.Content)
}
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 en Go
Fichiers externesEspace de travail et Modules GoPackages et ImportsNoms exportés vs non exportésIntroduction à la POO en GoLes Structs comme ClassesDéfinir des méthodes sur les StructsReceveurs par pointeur vs par valeurInitialisation de StructsFonctions constructeursRécapitulatif - Calculatrice simple4Interfaces
Introduction aux interfacesImplémentation impliciteL'interface comme contratInterface vide (any)Assertion de typeType SwitchComposition d'interfacesInterfaces Stringer & ErrorRécapitulatif - Calculateur de formes7Encapsulation
Champs exportés vs non exportésEncapsulation au niveau du packageMéthodes Getter & SetterMasquage d'informations en GoRécapitulatif - Dossiers étudiants10Génériques (Go 1.18+)
Introduction aux génériquesParamètres de typeContraintes de typeStructures génériquesContournement pour les méthodes génériquesRécapitulatif - Collections génériques13Patrons de conception, Partie 1
Introduction aux Design PatternsPatron SingletonPatron FactoryPatron Abstract FactoryPatron ObserverPatron Strategy2Exploration approfondie des Types et Structs
Types de base et compositesDéfinitions de types personnalisésTags de StructStructs anonymesStructs imbriquéesValeurs par défaut et Zero ValuesRécapitulatif - Carnet de contacts5Composition plutôt qu'héritage
Pourquoi Go n'a pas d'héritageLes bases de l'imbrication de structsPromotion de méthodesImbriquer plusieurs structsImbrication vs AgrégationMasquage de méthodes imbriquéesRécapitulatif - Hiérarchie des employés8Gestion des erreurs & POO
L'interface errorTypes d'erreurs personnalisésError Wrapping (fmt.Errorf)Erreurs sentinelleserrors.Is() et errors.As()Panic, Defer et RecoverRécapitulatif - File Parser11Bibliothèque standard et POO
io.Reader & io.WriterL'interface sort.InterfaceL'interface fmt.Stringerencoding/json avec les StructsL'interface http.HandlerRécapitulatif - Modèles d'API REST14Patrons de conception, Partie 2
Patron CommandePatron AdaptateurPatron DécorateurPatron Template MethodPatron ÉtatPatron CompositeMiddleware en tant que Décorateur3Pointeurs et Mémoire
Bases des pointeurs en GoPointeurs vers des structsPassage par valeur vs référenceLa fonction new()Garbage Collection en GoRécapitulatif - Constructeur de liste chaînée6Le polymorphisme en Go
Le polymorphisme via les interfacesLe Duck Typing en GoRègles de satisfaction des interfacesCollections polymorphesInjection de dépendancesRécapitulatif - Processeur de paiement9Concurrence et POO
Bases des GoroutinesChannels et CommunicationChannels avec buffer vs sans bufferL'instruction Selectsync.Mutex et sync.RWMutexsync.WaitGroupConception de structures Thread-SafeRécapitulatif - Worker Pool