Menu
Coddy logo textTech

Observer-Muster

Teil des Abschnitts Objektorientierte Programmierung der GO-Journey von Coddy — Lektion 89 von 107.

Das Observer-Muster definiert eine Eins-zu-viele-Beziehung zwischen Objekten. Wenn ein Objekt (das Subjekt) seinen Zustand ändert, werden alle seine Abhängigen (Beobachter) automatisch benachrichtigt. Dies ist ideal für Ereignissysteme, Benachrichtigungen oder jedes Szenario, in dem mehrere Komponenten auf Änderungen reagieren müssen.

In Go implementieren wir dieses Muster mithilfe eines Interfaces für Observer und eines Subject-Structs, das Abonnements verwaltet:

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

Jeder Typ, der das Observer-Interface implementiert, kann sich anmelden, um Benachrichtigungen zu erhalten:

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

Das Subjekt benachrichtigt alle Beobachter, ohne deren konkrete Typen zu kennen:

subject := &Subject{}
subject.Subscribe(EmailAlert{Address: "user@example.com"})
subject.Subscribe(Logger{})

subject.Notify("Order placed")
// E-Mail an user@example.com: Order placed
// Log: Order placed

Dieses Muster entkoppelt das Subjekt von seinen Beobachtern, wodurch es einfach wird, neue Beobachtertypen hinzuzufügen, ohne den bestehenden Code zu ändern. Es wird häufig in ereignisgesteuerten Architekturen, GUI-Frameworks und Pub/Sub-Messaging-Systemen verwendet.

challenge icon

Aufgabe

Einfach

Lass uns ein Aktienpreis-Alarmsystem mit dem Observer-Pattern bauen! Du wirst ein System erstellen, bei dem mehrere Alarmdienste automatisch Benachrichtigungen erhalten, wann immer sich ein Aktienkurs ändert – perfekt, um zu demonstrieren, wie Observer auf Ereignisse reagieren können, ohne dass das Subjekt deren konkrete Typen kennen muss.

Du wirst deinen Code über drei Dateien organisieren:

  • observer.go: Definiere dein Observer-Interface und die konkreten Observer-Typen.

    Erstelle ein Observer-Interface mit einer Methode Update(stockName string, price float64), die Aktieninformationen erhält, wenn sich die Preise ändern.

    Implementiere zwei Observer-Typen:

    • PriceAlert mit einem Threshold-Feld (float64) – wenn es aktualisiert wird, gibt es ALERT: [stockName] at $[price] crossed threshold $[threshold] zurück, falls der Preis über dem Schwellenwert liegt, oder WATCHING: [stockName] at $[price] (threshold: $[threshold]), falls er darunter liegt.
    • PriceLogger mit einem LogName-Feld (string) – wenn es aktualisiert wird, gibt es [logName] recorded [stockName]: $[price] zurück.

    Beide Observer sollten eine Methode haben, die ihren formatierten Antwort-String zurückgibt.

  • stock.go: Erstelle dein Subjekt, das die Observer verwaltet und sie über Preisänderungen benachrichtigt.

    Erstelle ein Stock-Struct mit Feldern für Name (string), Price (float64) und einem Slice von Observern. Füge diese Methoden hinzu:

    • Subscribe(o Observer), um einen Observer hinzuzufügen
    • SetPrice(price float64) []string, das den Preis aktualisiert, alle Observer benachrichtigt und ein Slice mit all ihren Antworten zurückgibt

    Erstelle außerdem einen NewStock(name string, initialPrice float64) *Stock-Konstruktor.

  • main.go: Richte deine Aktie mit Observern ein und simuliere Preisänderungen.

    Lies den Aktiennamen und den Anfangspreis ein. Lies dann die Anzahl der zu erstellenden Observer ein. Lies für jeden Observer seinen Typ (alert oder logger) und seine Konfiguration (Schwellenwert für Alerts, Log-Name für Loggers) ein. Abonniere jeden Observer bei der Aktie.

    Lies schließlich einen neuen Preis ein, aktualisiere die Aktie und gib die Antwort jedes Observers in einer eigenen Zeile aus.

Die folgenden Eingaben werden bereitgestellt:

  • Zeile 1: Aktienname
  • Zeile 2: Anfangspreis
  • Zeile 3: Anzahl der Observer
  • Für jeden Observer: Typ (alert oder logger), dann Konfigurationswert
  • Letzte Zeile: Neuer zu setzender Preis

Formatiere alle Preise mit zwei Dezimalstellen.

Zum Beispiel, gegeben:

GOOG
150.00
3
alert
155.00
logger
TradeLog
alert
140.00
160.50

Deine Ausgabe sollte sein:

ALERT: GOOG at $160.50 crossed threshold $155.00
TradeLog recorded GOOG: $160.50
ALERT: GOOG at $160.50 crossed threshold $140.00

Und gegeben:

AAPL
180.00
2
logger
DailyLog
alert
200.00
175.25

Deine Ausgabe sollte sein:

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

Beachte, dass die Aktie (Stock) nicht weiß, ob sie Alerts oder Loggers benachrichtigt – sie ruft einfach Update für jeden Observer auf. Diese Entkopplung macht es einfach, neue Observer-Typen hinzuzufügen, ohne den Code von Stock zu ändern!

Spickzettel

Das Observer-Muster definiert eine Eins-zu-viele-Beziehung, bei der ein Subjekt mehrere Beobachter automatisch benachrichtigt, wenn sich sein Zustand ändert. Dies entkoppelt das Subjekt von seinen Beobachtern und macht es ideal für Ereignissysteme und Benachrichtigungen.

Grundstruktur

Definieren Sie ein Observer-Interface und ein Subjekt, das Abonnements verwaltet:

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

Implementierung von Objekten

Jeder Typ, der das Observer-Interface implementiert, kann sich anmelden:

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

Verwendung

Melden Sie Beobachter an und benachrichtigen Sie diese über Änderungen:

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

Das Subjekt benachrichtigt alle Beobachter, ohne deren konkrete Typen zu kennen, was es einfach macht, neue Beobachtertypen hinzuzufügen, ohne bestehenden Code zu ändern.

Probier es selbst

package main

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

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

	// Aktiennamen lesen
	stockName, _ := reader.ReadString('\n')
	stockName = strings.TrimSpace(stockName)

	// Anfangspreis lesen
	initialPriceStr, _ := reader.ReadString('\n')
	initialPrice, _ := strconv.ParseFloat(strings.TrimSpace(initialPriceStr), 64)

	// Anzahl der Beobachter lesen
	numObserversStr, _ := reader.ReadString('\n')
	numObservers, _ := strconv.Atoi(strings.TrimSpace(numObserversStr))

	// TODO: Erstelle eine neue Stock mit dem NewStock-Konstruktor

	// TODO: Durchlaufe jeden Beobachter in einer Schleife
	for i := 0; i < numObservers; i++ {
		// Beobachtertyp lesen ("alert" oder "logger")
		observerType, _ := reader.ReadString('\n')
		observerType = strings.TrimSpace(observerType)

		// Konfigurationswert lesen
		configValue, _ := reader.ReadString('\n')
		configValue = strings.TrimSpace(configValue)

		// TODO: Erstelle den passenden Beobachter basierend auf dem Typ
		// - Für "alert": configValue als float64-Schwellenwert parsen, PriceAlert erstellen
		// - Für "logger": configValue als LogName verwenden, PriceLogger erstellen
		// TODO: Melde den Beobachter bei der Aktie an
	}

	// Neuen Preis lesen
	newPriceStr, _ := reader.ReadString('\n')
	newPrice, _ := strconv.ParseFloat(strings.TrimSpace(newPriceStr), 64)

	// TODO: Setze den neuen Preis und erhalte die Antworten

	// TODO: Gib jede Antwort in einer eigenen Zeile aus
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung