Menu
Coddy logo textTech

Pattern 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 un-à-plusieurs entre les objets. Lorsqu’un objet (le sujet) change d’état, tous ses dépendants (les observateurs) sont automatiquement informés. Cela convient parfaitement aux systèmes d’événements, aux notifications ou à tout scénario dans lequel plusieurs composants doivent réagir aux changements.

En Go, nous implémentons ce modèle à l’aide d’une interface pour les observateurs et d’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 qui implémente 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 : Commande passée
// Log : Commande passée

Ce modèle 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 d’interface graphique et les systèmes de messagerie pub/sub.

challenge icon

Défi

Facile

Construisons un système d’alerte de cours d’actions à l’aide du modèle Observer ! Tu vas créer un système dans lequel plusieurs services d’alerte reçoivent automatiquement des notifications chaque fois que le cours d’une action change, ce qui est idéal pour montrer comment les observateurs peuvent réagir aux événements sans que le sujet connaisse leurs types concrets.

Tu organiseras ton code sur trois fichiers :

  • observer.go : Define ton interface d’observateur et les types d’observateurs concrets.

    Create une interface Observer avec un method Update(stockName string, price float64) qui reçoit les informations de l’action lorsque les cours changent.

    Implement deux types d’observateurs :

    • PriceAlert avec un champ Threshold (float64) ; lors d’une mise à jour, il renvoie ALERT: [stockName] at $[price] crossed threshold $[threshold] si le cours est supérieur au seuil, ou WATCHING: [stockName] at $[price] (threshold: $[threshold]) s’il est inférieur
    • PriceLogger avec un champ LogName (string) ; lors d’une mise à jour, il renvoie [logName] recorded [stockName]: $[price]

    Les deux observateurs doivent avoir un method qui renvoie leur chaîne de réponse formatée.

  • stock.go : Create ton sujet, qui gère les observateurs et les informe des changements de cours.

    Construis une structure Stock avec des fields pour Name (string), Price (float64) et une slice d’observateurs. Add ces méthodes :

    • Subscribe(o Observer) pour add un observateur
    • SetPrice(price float64) []string qui met à jour le cours et informe tous les observateurs, en renvoyant une slice de toutes leurs réponses

    Create également un constructeur NewStock(name string, initialPrice float64) *Stock.

  • main.go : Configure ton action avec des observateurs et simule des changements de cours.

    Lis le nom de l’action et son cours initial. Lis ensuite un nombre d’observateurs à créer. Pour chaque observateur, lis son type (alert ou logger) et sa configuration (le seuil pour les alertes, le nom du journal pour les loggers). Abonne chaque observateur à l’action.

    Enfin, lis un nouveau cours, mets à jour l’action et affiche la réponse de chaque observateur sur sa propre ligne.

Les entrées suivantes seront fournies :

  • Line 1 : nom de l’action
  • Line 2 : cours initial
  • Line 3 : Number d’observateurs
  • Pour chaque observateur : type (alert ou logger), puis valeur de configuration
  • Line finale : nouveau cours à définir

Format tous les cours avec deux décimales.

Par exemple, given :

GOOG
150.00
3
alert
155.00
logger
TradeLog
alert
140.00
160.50

Ta sortie doit ê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 given :

AAPL
180.00
2
logger
DailyLog
alert
200.00
175.25

Ta sortie doit être :

DailyLog recorded AAPL: $175.25
WATCHING: AAPL at $175.25 (threshold: $200.00)

Remarque comment Stock ne sait pas s’il informe 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 !

Essayez vous-même

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// Lire le nom du stock
	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" or "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é selon le type
		// - Pour "alert" : analyser configValue comme 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: Afficher 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

Entraînez-vous par vous-même : Compilateur Go en ligne