Menu
Coddy logo textTech

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 placed

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

challenge icon

Défi

Facile

Construisons 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 Observer avec une méthode Update(stockName string, price float64) qui reçoit les informations sur l'action lorsque les prix changent.

    Implémentez deux types d'observateurs :

    • PriceAlert avec un champ Threshold (float64) — lors de la mise à jour, il retourne ALERT: [stockName] at $[price] crossed threshold $[threshold] si le prix est supérieur au seuil, ou WATCHING: [stockName] at $[price] (threshold: $[threshold]) s'il est inférieur
    • PriceLogger avec un champ LogName (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 Stock avec des champs pour Name (string), Price (float64), et une tranche (slice) d'observateurs. Ajoutez ces méthodes :

    • Subscribe(o Observer) pour ajouter un observateur
    • SetPrice(price float64) []string qui 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 (alert ou logger) 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 (alert ou logger), 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.50

Votre 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.00

Et étant donné :

AAPL
180.00
2
logger
DailyLog
alert
200.00
175.25

Votre 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 placed

Le 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
}
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