Menu
CoddyTech

Modèle Commande

Fait partie de la section Programmation Orientée Objet du Journey GO de Coddy. Leçon 91 sur 107.

Le patron Command encapsule une requête sous forme d’objet, ce qui vous permet 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, Command encapsule des actions entières ainsi que leurs paramètres.

En Go, nous définissons une interface Command avec une méthode Execute, puis créons des commandes concrètes qui contiennent 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 invocateur 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’invocateur 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 éteinte

Command est idéal pour mettre en œuvre 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.

challenge icon

Défi

Facile

Construisons un éditeur de texte avec une fonctionnalité d’annulation à l’aide du patron Command ! Tu vas créer des commandes qui encapsulent les opérations sur le texte, permettant à l’éditeur d’exécuter des actions et de les inverser : un cas d’utilisation classique qui montre pourquoi les commandes sont si puissantes.

Tu vas organiser ton code dans trois fichiers :

  • command.go : définis ton interface de commande et les commandes concrètes de modification du texte.

    Crée une interface Command avec deux méthodes : Execute() string pour effectuer l’action et Undo() string pour l’inverser.

    Implémente deux types de commande qui agissent sur un *TextEditor :

    • InsertCommand : contient le pointeur vers l’éditeur et le texte à insérer. Execute ajoute le texte au contenu de l’éditeur et renvoie Inserted: [text]. Undo supprime ce texte à la fin et renvoie Undone insert: [text]
    • DeleteCommand : contient le pointeur vers l’éditeur et le nombre de caractères à supprimer à la fin. Execute supprime ces caractères (en les stockant pour permettre l’annulation) et renvoie Deleted: [removed text]. Undo les restaure et renvoie Undone delete: [restored text]
  • editor.go : crée ton éditeur de texte (le récepteur) et l’invocateur qui gère l’historique des commandes.

    Construis une structure TextEditor avec un champ Content (de type chaîne) et les méthodes Append(text string) et DeleteLast(count int) string (qui renvoie le texte supprimé).

    Construis une structure EditorInvoker qui contient une tranche des commandes exécutées, servant d’historique. Ajoute ces méthodes :

    • ExecuteCommand(cmd Command) string : exécute la commande, l’ajoute à l’historique et renvoie le résultat
    • UndoLast() string : supprime la dernière commande de l’historique, appelle sa méthode Undo et renvoie le résultat. Si l’historique est vide, renvoie Nothing to undo
  • main.go : montre le fonctionnement de ton système de commandes avec une série d’opérations.

    Lis le nombre d’opérations. Pour chaque opération, lis son type (insert, delete ou undo). Pour une insertion, lis également le texte à insérer. Pour une suppression, lis le nombre de caractères. Exécute chaque opération via ton invocateur et affiche le résultat. Après toutes les opérations, affiche le contenu final de l’éditeur sous la forme Final: [content].

Les entrées suivantes seront fournies :

  • Ligne 1 : nombre d’opérations
  • Pour chaque opération : le type d’opération, puis les données supplémentaires si nécessaire (texte à insérer, nombre de caractères à supprimer)

Par exemple, étant donné :

5
insert
Hello
insert
 World
delete
3
undo
undo

Ta sortie devrait être :

Inserted: Hello
Inserted:  World
Deleted: rld
Undone delete: rld
Undone insert:  World
Final: Hello

Et étant donné :

4
insert
Go
insert
Lang
undo
insert
!

Ta sortie devrait être :

Inserted: Go
Inserted: Lang
Undone insert: Lang
Inserted: !
Final: Go!

Et étant donné :

2
undo
insert
Test

Ta sortie devrait être :

Nothing to undo
Inserted: Test
Final: Test

Remarque que chaque commande encapsule tout ce qui est nécessaire pour effectuer son action et l’inverser. L’invocateur ne sait pas ce que font les commandes. Il les exécute simplement et gère l’historique pour permettre l’annulation !

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)
}
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 Go en ligne