Menu
Coddy logo textTech

Observer-Muster

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

Das Observer pattern definiert eine Eins-zu-viele-Beziehung zwischen Objekten. Wenn ein Objekt (das Subjekt) seinen Zustand ändert, werden alle seine abhängigen Objekte (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 Beobachter und einer Subject-Struktur, die 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 die Schnittstelle Observer 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 ihre konkreten 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: Bestellung aufgegeben
// Log: Bestellung aufgegeben

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

challenge icon

Aufgabe

Einfach

Erstellen wir ein Aktienkurs-Warnsystem mit dem Observer-Muster! Du erstellst ein System, in dem mehrere Warn-Dienste automatisch Benachrichtigungen erhalten, sobald sich ein Aktienkurs ändert – perfekt, um zu demonstrieren, wie Observer auf events reagieren können, ohne dass das Subjekt ihre konkreten Typen kennt.

Du organisierst deinen Code auf drei Dateien verteilt:

  • observer.go: Define dein Observer-interface und konkrete Observer-Typen.

    Erstelle ein Observer-interface mit einer Update(stockName string, price float64)-method, die bei Kursänderungen Aktieninformationen empfängt.

    Implementiere zwei Observer-Typen:

    • PriceAlert mit einem Threshold-field (float64); bei einer Aktualisierung gibt es ALERT: [stockName] at $[price] crossed threshold $[threshold] zurück, wenn der Kurs über dem Schwellenwert liegt, oder WATCHING: [stockName] at $[price] (threshold: $[threshold]), wenn er darunter liegt
    • PriceLogger mit einem LogName-field (string); bei einer Aktualisierung gibt es [logName] recorded [stockName]: $[price] zurück

    Beide Observer sollten eine method besitzen, die ihren formatierten Antworttext zurückgibt.

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

    Erstelle eine Stock-Struktur mit den fields 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, die den Kurs aktualisiert und alle Observer benachrichtigt; sie gibt einen Slice mit all ihren Antworten zurück

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

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

    Lies den Aktiennamen und den initialen Kurs ein. Lies anschließend die Anzahl der zu erstellenden Observer ein. Lies für jeden Observer seinen Typ (alert oder logger) und seine configuration ein (Schwellenwert für Warnungen, Log-Name für Logger). Abonniere jeden Observer bei der Aktie.

    Lies schließlich einen neuen Kurs 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: Initialer Kurs
  • Zeile 3: Anzahl der Observer
  • Für jeden Observer: Typ (alert oder logger), danach configuration value
  • Letzte Zeile: Zu setzender neuer Kurs

Formatiere alle Kurse mit zwei Dezimalstellen.

Beispielsweise bei folgenden Eingaben:

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 bei folgenden Eingaben:

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 Stock nicht weiß, ob es Alerts oder Logger benachrichtigt. Es ruft einfach bei jedem Observer Update auf. Diese Entkopplung macht es einfach, neue Observer-Typen hinzuzufügen, ohne den Stock-Code zu ändern!

Probier es selbst

package main

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

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

	// Aktienname 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: Schleife über jeden Beobachter
	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 entsprechenden Beobachter basierend auf dem Typ
		// - Für "alert": parse configValue als float64-Schwellenwert, erstelle PriceAlert
		// - Für "logger": verwende configValue als LogName, erstelle PriceLogger
		// TODO: Abonniere den Beobachter am Stock
	}

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

	// TODO: Setze den neuen Preis und hole 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

Übe selbstständig: Online-Go-Compiler