Menu
Coddy logo textTech

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 avec 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 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.

challenge icon

Défi

Facile

Construisons un éditeur de texte avec une fonctionnalité d’annulation en utilisant le pattern 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 annuler : un cas d’utilisation classique qui montre pourquoi les commandes sont si puissantes.

Tu vas organiser ton code sur trois fichiers :

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

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

    Implémente deux types de commandes qui opèrent 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 de la fin et renvoie Undone insert: [text]
    • DeleteCommand : contient le pointeur vers l’éditeur et un nombre de caractères à supprimer à la fin. Execute supprime ces caractères (en les conservant pour 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 (string) et des méthodes Append(text string) et DeleteLast(count int) string (qui renvoie le texte supprimé).

    Construis une structure EditorInvoker qui contient une tranche de 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 ton système de commandes avec une série d’opérations.

    Lis un nombre d’opérations. Pour chaque opération, lis le type d’opération (insert, delete ou undo). Pour insert, lis également le texte à insérer. Pour delete, 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 : type d’opération, puis les données supplémentaires si nécessaire (texte pour insert, nombre pour delete)

Par exemple, avec :

5
insert
Hello
insert
 World
delete
3
undo
undo

Ta sortie doit être :

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

Et avec :

4
insert
Go
insert
Lang
undo
insert
!

Ta sortie doit être :

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

Et avec :

2
undo
insert
Test

Ta sortie doit être :

Nothing to undo
Inserted: Test
Final: Test

Remarque comment chaque commande encapsule tout ce qui est nécessaire pour effectuer et annuler son action. L’invocateur ne sait pas ce que font les commandes. Il les exécute simplement et maintient 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'editor et l'invoker
	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 un InsertCommand et l'exécuter via l'invoker
			_ = text
			_ = editor
			
		case "delete":
			// Lire le nombre de caractères à supprimer
			countLine, _ := reader.ReadString('\n')
			count, _ := strconv.Atoi(strings.TrimSpace(countLine))
			
			// TODO: Créer un DeleteCommand et l'exécuter via l'invoker
			_ = count
			
		case "undo":
			// TODO: Appeler UndoLast sur l'invoker
			
		}
		
		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