Menu
Coddy logo textTech

Patron État

Fait partie de la section Programmation Orientée Objet du Journey GO de Coddy — leçon 95 sur 107.

Le pattern State permet à un objet de modifier son comportement lorsque son état interne change, donnant l'impression que l'objet a changé de classe. Alors que le Template Method contrôle les étapes d'un algorithme, State encapsule le comportement spécifique à l'état dans des objets distincts et délègue à l'état actuel.

En Go, nous définissons une interface d'état et créons des états concrets qui implémentent différents comportements :

type State interface {
    Handle(d *Document) string
}

type Document struct {
    state State
}

func (d *Document) SetState(s State) {
    d.state = s
}

func (d *Document) Publish() string {
    return d.state.Handle(d)
}

Chaque état détermine ce qui se passe et quel état suit :

type DraftState struct{}

func (s DraftState) Handle(d *Document) string {
    d.SetState(ModerationState{})
    return "Draft submitted for moderation"
}

type ModerationState struct{}

func (s ModerationState) Handle(d *Document) string {
    d.SetState(PublishedState{})
    return "Moderation approved, now published"
}

type PublishedState struct{}

func (s PublishedState) Handle(d *Document) string {
    return "Already published"
}

Le même appel de méthode produit des résultats différents selon l'état actuel :

doc := &Document{state: DraftState{}}

fmt.Println(doc.Publish())  // Brouillon soumis pour modération
fmt.Println(doc.Publish())  // Modération approuvée, maintenant publié
fmt.Println(doc.Publish())  // Déjà publié

L'état (State) est idéal pour les objets ayant des modes de fonctionnement distincts, tels que les flux de travail de traitement des commandes, les composants d'interface utilisateur ou les gestionnaires de connexion où le comportement dépend entièrement de l'état actuel.

challenge icon

Défi

Facile

Construisons un système de support de tickets en utilisant le modèle State ! Vous allez créer un ticket de support qui passe par différentes étapes — de l'ouverture au traitement, puis à la résolution — chaque état déterminant les actions possibles et ce qui se passe ensuite.

Vous organiserez votre code sur trois fichiers :

  • state.go : Définissez votre interface d'état et les états concrets qui représentent chaque étape du cycle de vie d'un ticket.

    Créez une interface TicketState avec une méthode Handle(t *Ticket) string qui traite le ticket et effectue potentiellement une transition vers l'état suivant.

    Implémentez trois états :

    • OpenState — lorsqu'il est traité, fait passer le ticket à InProgressState et retourne Ticket opened, assigning to support team
    • InProgressState — lorsqu'il est traité, fait passer à ResolvedState et retourne Working on ticket, issue resolved
    • ResolvedState — lorsqu'il est traité, reste dans le même état et retourne Ticket already resolved
  • ticket.go : Créez votre structure de ticket qui contient l'état actuel et lui délègue le comportement.

    Construisez une structure Ticket avec un champ ID (string) et un champ state (TicketState). Ajoutez ces méthodes :

    • SetState(s TicketState) pour changer l'état actuel du ticket
    • Process() string qui délègue à la méthode Handle de l'état actuel

    Créez un constructeur NewTicket(id string) *Ticket qui retourne un ticket commençant dans l'état OpenState.

  • main.go : Démontrez comment la même action produit des résultats différents selon l'état actuel du ticket.

    Lisez un ID de ticket et le nombre de fois que le ticket doit être traité. Créez un nouveau ticket avec cet ID, puis appelez Process() le nombre de fois spécifié, en affichant chaque résultat sur une ligne distincte.

Les entrées suivantes seront fournies :

  • Ligne 1 : ID du ticket
  • Ligne 2 : Nombre de fois que le ticket doit être traité

Par exemple, étant donné :

TKT-001
3

Votre sortie devrait être :

Ticket opened, assigning to support team
Working on ticket, issue resolved
Ticket already resolved

Et étant donné :

TKT-500
5

Votre sortie devrait être :

Ticket opened, assigning to support team
Working on ticket, issue resolved
Ticket already resolved
Ticket already resolved
Ticket already resolved

Et étant donné :

ISSUE-42
1

Votre sortie devrait être :

Ticket opened, assigning to support team

Remarquez comment l'appel de Process() sur le même ticket produit des résultats différents à chaque fois — le comportement du ticket change au fur et à mesure qu'il passe par les différents états. Une fois résolu, il reste résolu quel que soit le nombre de fois que vous le traitez. L'objet ticket semble changer de comportement, mais il délègue en réalité à différents objets d'état !

Aide-mémoire

Le pattern State permet à un objet de modifier son comportement lorsque son état interne change. L'objet délègue le comportement à des objets d'état distincts représentant différents états.

Définissez une interface d'état :

type State interface {
    Handle(d *Document) string
}

Créez un objet de contexte qui contient l'état actuel :

type Document struct {
    state State
}

func (d *Document) SetState(s State) {
    d.state = s
}

func (d *Document) Publish() string {
    return d.state.Handle(d)
}

Implémentez des états concrets qui encapsulent le comportement spécifique à chaque état :

type DraftState struct{}

func (s DraftState) Handle(d *Document) string {
    d.SetState(ModerationState{})
    return "Draft submitted for moderation"
}

type ModerationState struct{}

func (s ModerationState) Handle(d *Document) string {
    d.SetState(PublishedState{})
    return "Moderation approved, now published"
}

type PublishedState struct{}

func (s PublishedState) Handle(d *Document) string {
    return "Already published"
}

Chaque état détermine ce qui se passe et effectue la transition vers l'état suivant :

doc := &Document{state: DraftState{}}

fmt.Println(doc.Publish())  // Brouillon soumis pour modération
fmt.Println(doc.Publish())  // Modération approuvée, maintenant publié
fmt.Println(doc.Publish())  // Déjà publié

Le pattern State est idéal pour les objets ayant des modes de fonctionnement distincts où le comportement dépend entièrement de l'état actuel.

Essayez vous-même

package main

import (
	"fmt"
)

func main() {
	// Lire l'entrée
	var ticketID string
	var numProcesses int
	fmt.Scanln(&ticketID)
	fmt.Scanln(&numProcesses)

	// TODO: Créer un nouveau ticket avec l'ID donné

	// TODO: Traiter le ticket le nombre de fois spécifié
	// et afficher chaque résultat sur une ligne séparée
}
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