Patron Observer
Fait partie de la section Programmation Orientée Objet du Journey GO de Coddy — leçon 89 sur 107.
Le pattern Observer définit une relation de un à plusieurs entre les objets. Lorsqu'un objet (le sujet) change d'état, tous ses dépendants (observateurs) sont informés automatiquement. C'est idéal pour les systèmes d'événements, les notifications ou tout scénario où plusieurs composants doivent réagir aux changements.
En Go, nous implémentons ce patron en utilisant une interface pour les observateurs et une structure de sujet qui gère les abonnements :
type Observer interface {
Update(event string)
}
type Subject struct {
observers []Observer
}
func (s *Subject) Subscribe(o Observer) {
s.observers = append(s.observers, o)
}
func (s *Subject) Notify(event string) {
for _, observer := range s.observers {
observer.Update(event)
}
}Tout type implémentant l'interface Observer peut s'abonner pour recevoir des notifications :
type EmailAlert struct{ Address string }
func (e EmailAlert) Update(event string) {
fmt.Printf("Email to %s: %s\n", e.Address, event)
}
type Logger struct{}
func (l Logger) Update(event string) {
fmt.Println("Log:", event)
}Le sujet notifie tous les observateurs sans connaître leurs types concrets :
subject := &Subject{}
subject.Subscribe(EmailAlert{Address: "user@example.com"})
subject.Subscribe(Logger{})
subject.Notify("Order placed")
// Email à user@example.com : Order placed
// Log : Order placedCe patron découple le sujet de ses observateurs, ce qui facilite l'ajout de nouveaux types d'observateurs sans modifier le code existant. Il est couramment utilisé dans les architectures pilotées par les événements, les frameworks GUI et les systèmes de messagerie pub/sub.
Défi
FacileConstruisons un système d'alerte de prix d'actions en utilisant le pattern Observer ! Vous allez créer un système où plusieurs services d'alerte reçoivent automatiquement des notifications chaque fois qu'un prix d'action change — parfait pour démontrer comment les observateurs peuvent réagir à des événements sans que le sujet ne connaisse leurs types concrets.
Vous organiserez votre code sur trois fichiers :
observer.go: Définissez votre interface d'observateur et les types d'observateurs concrets.Créez une interface
Observeravec une méthodeUpdate(stockName string, price float64)qui reçoit les informations sur l'action lorsque les prix changent.Implémentez deux types d'observateurs :
PriceAlertavec un champThreshold(float64) — lors de la mise à jour, il retourneALERT: [stockName] at $[price] crossed threshold $[threshold]si le prix est supérieur au seuil, ouWATCHING: [stockName] at $[price] (threshold: $[threshold])s'il est inférieurPriceLoggeravec un champLogName(string) — lors de la mise à jour, il retourne[logName] recorded [stockName]: $[price]
Les deux observateurs doivent avoir une méthode qui retourne leur chaîne de réponse formatée.
stock.go: Créez votre sujet qui gère les observateurs et les notifie des changements de prix.Construisez une structure
Stockavec des champs pourName(string),Price(float64), et une tranche (slice) d'observateurs. Ajoutez ces méthodes :Subscribe(o Observer)pour ajouter un observateurSetPrice(price float64) []stringqui met à jour le prix et notifie tous les observateurs, en retournant une tranche de toutes leurs réponses
Créez également un constructeur
NewStock(name string, initialPrice float64) *Stock.main.go: Configurez votre action avec des observateurs et simulez des changements de prix.Lisez le nom de l'action et le prix initial. Ensuite, lisez un nombre d'observateurs à créer. Pour chaque observateur, lisez son type (
alertoulogger) et sa configuration (seuil pour les alertes, nom du journal pour les loggers). Abonnez chaque observateur à l'action.Enfin, lisez un nouveau prix, mettez à jour l'action, et affichez la réponse de chaque observateur sur sa propre ligne.
Les entrées suivantes seront fournies :
- Ligne 1 : Nom de l'action
- Ligne 2 : Prix initial
- Ligne 3 : Nombre d'observateurs
- Pour chaque observateur : type (
alertoulogger), puis valeur de configuration - Dernière ligne : Nouveau prix à fixer
Formatez tous les prix avec deux décimales.
Par exemple, étant donné :
GOOG
150.00
3
alert
155.00
logger
TradeLog
alert
140.00
160.50Votre sortie devrait être :
ALERT: GOOG at $160.50 crossed threshold $155.00
TradeLog recorded GOOG: $160.50
ALERT: GOOG at $160.50 crossed threshold $140.00Et étant donné :
AAPL
180.00
2
logger
DailyLog
alert
200.00
175.25Votre sortie devrait être :
DailyLog recorded AAPL: $175.25
WATCHING: AAPL at $175.25 (threshold: $200.00)Remarquez comment le Stock ne sait pas s'il notifie des alertes ou des loggers — il appelle simplement Update sur chaque observateur. Ce découplage facilite l'ajout de nouveaux types d'observateurs sans modifier le code de Stock !
Aide-mémoire
Le pattern Observer (ou Observateur) définit une relation un-à-plusieurs où un sujet notifie automatiquement plusieurs observateurs lorsque son état change. Cela découple le sujet de ses observateurs, ce qui le rend idéal pour les systèmes d'événements et les notifications.
Structure de base
Définissez une interface d'observateur et un sujet qui gère les abonnements :
type Observer interface {
Update(event string)
}
type Subject struct {
observers []Observer
}
func (s *Subject) Subscribe(o Observer) {
s.observers = append(s.observers, o)
}
func (s *Subject) Notify(event string) {
for _, observer := range s.observers {
observer.Update(event)
}
}Implémentation des observateurs
Tout type implémentant l'interface Observer peut s'abonner :
type EmailAlert struct{ Address string }
func (e EmailAlert) Update(event string) {
fmt.Printf("Email to %s: %s\n", e.Address, event)
}
type Logger struct{}
func (l Logger) Update(event string) {
fmt.Println("Log:", event)
}Utilisation
Abonnez des observateurs et notifiez-les des changements :
subject := &Subject{}
subject.Subscribe(EmailAlert{Address: "user@example.com"})
subject.Subscribe(Logger{})
subject.Notify("Order placed")
// Email to user@example.com: Order placed
// Log: Order placedLe sujet notifie tous les observateurs sans connaître leurs types concrets, ce qui facilite l'ajout de nouveaux types d'observateurs sans modifier le code existant.
Essayez vous-même
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Lire le nom de l'action
stockName, _ := reader.ReadString('\n')
stockName = strings.TrimSpace(stockName)
// Lire le prix initial
initialPriceStr, _ := reader.ReadString('\n')
initialPrice, _ := strconv.ParseFloat(strings.TrimSpace(initialPriceStr), 64)
// Lire le nombre d'observateurs
numObserversStr, _ := reader.ReadString('\n')
numObservers, _ := strconv.Atoi(strings.TrimSpace(numObserversStr))
// TODO: Créer un nouveau Stock en utilisant le constructeur NewStock
// TODO: Parcourir chaque observateur
for i := 0; i < numObservers; i++ {
// Lire le type d'observateur ("alert" ou "logger")
observerType, _ := reader.ReadString('\n')
observerType = strings.TrimSpace(observerType)
// Lire la valeur de configuration
configValue, _ := reader.ReadString('\n')
configValue = strings.TrimSpace(configValue)
// TODO: Créer l'observateur approprié en fonction du type
// - Pour "alert" : analyser configValue comme un seuil float64, créer PriceAlert
// - Pour "logger" : utiliser configValue comme LogName, créer PriceLogger
// TODO: Abonner l'observateur au stock
}
// Lire le nouveau prix
newPriceStr, _ := reader.ReadString('\n')
newPrice, _ := strconv.ParseFloat(strings.TrimSpace(newPriceStr), 64)
// TODO: Définir le nouveau prix et obtenir les réponses
// TODO: Imprimer chaque réponse sur sa propre ligne
}
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