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.
Défi
FacileConstruisons 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
TicketStateavec une méthodeHandle(t *Ticket) stringqui 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 àInProgressStateet retourneTicket opened, assigning to support teamInProgressState— lorsqu'il est traité, fait passer àResolvedStateet retourneWorking on ticket, issue resolvedResolvedState— lorsqu'il est traité, reste dans le même état et retourneTicket 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
Ticketavec un champID(string) et un champstate(TicketState). Ajoutez ces méthodes :SetState(s TicketState)pour changer l'état actuel du ticketProcess() stringqui délègue à la méthode Handle de l'état actuel
Créez un constructeur
NewTicket(id string) *Ticketqui retourne un ticket commençant dans l'étatOpenState.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
3Votre sortie devrait être :
Ticket opened, assigning to support team
Working on ticket, issue resolved
Ticket already resolvedEt étant donné :
TKT-500
5Votre sortie devrait être :
Ticket opened, assigning to support team
Working on ticket, issue resolved
Ticket already resolved
Ticket already resolved
Ticket already resolvedEt étant donné :
ISSUE-42
1Votre sortie devrait être :
Ticket opened, assigning to support teamRemarquez 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
}
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