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 aufgegebenDieses 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.
Aufgabe
EinfachErstellen 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-interfaceund konkrete Observer-Typen.Erstelle ein
Observer-interfacemit einerUpdate(stockName string, price float64)-method, die bei Kursänderungen Aktieninformationen empfängt.Implementiere zwei Observer-Typen:
PriceAlertmit einemThreshold-field(float64); bei einer Aktualisierung gibt esALERT: [stockName] at $[price] crossed threshold $[threshold]zurück, wenn der Kurs über dem Schwellenwert liegt, oderWATCHING: [stockName] at $[price] (threshold: $[threshold]), wenn er darunter liegtPriceLoggermit einemLogName-field(string); bei einer Aktualisierung gibt es[logName] recorded [stockName]: $[price]zurück
Beide Observer sollten eine
methodbesitzen, 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 denfieldsName(string),Price(float64) und einem Slice von Observern. Füge diese Methoden hinzu:Subscribe(o Observer), um einen Observer hinzuzufügenSetPrice(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 (
alertoderlogger) 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 (
alertoderlogger), 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.50Deine 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.00Und bei folgenden Eingaben:
AAPL
180.00
2
logger
DailyLog
alert
200.00
175.25Deine 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
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP in Go
Externe DateienGo-Workspace & ModulePackages & ImportsExportierte vs. nicht exportierte NamenEinführung in OOP mit GoStructs als KlassenMethoden für Structs definierenPointer- vs. Value-ReceiverStruct-InitialisierungKonstruktorfunktionenRückblick – Einfacher Taschenrechner4Schnittstellen
Einführung in SchnittstellenImplizite ImplementierungSchnittstelle als VertragLeere Schnittstelle (any)TypzusicherungTypwechselZusammensetzung von SchnittstellenStringer- und Error-SchnittstellenRückblick – Formenrechner7Kapselung
Exportierte vs. nicht exportierte FelderKapselung auf PaketebeneGetter- und Setter-MethodenInformationsverbergung in GoRückblick – Studierendendatensätze10Generics (Go 1.18+)
Einführung in GenericsTypparameterTypbeschränkungenGenerische StrukturenWorkaround für generische MethodenZusammenfassung – Generische Sammlung13Entwurfsmuster Teil 1
Einführung in EntwurfsmusterSingleton-MusterFactory-MusterAbstraktes Factory-MusterObserver-MusterStrategy-Muster2Typen & Structs im Detail
Grundlegende & zusammengesetzte TypenBenutzerdefinierte TypdefinitionenStruct-TagsAnonyme StructsVerschachtelte StructsNullwerte & StandardwerteRückblick – Kontaktbuch5Komposition statt Vererbung
Warum Go keine Vererbung hatGrundlagen der Struct-EinbettungMethoden-PromotionMehrere Structs einbettenEinbettung vs. AggregationVerbergen eingebetteter MethodenRückblick – Mitarbeiterhierarchie8Fehlerbehandlung & OOP
Das Error-InterfaceBenutzerdefinierte FehlertypenError-Wrapping (fmt.Errorf)Sentinel-Fehlererrors.Is() und errors.As()Panic, Defer und RecoverRückblick – Dateiparser3Zeiger & Speicher
Grundlagen von Zeigern in GoZeiger auf StructsÜbergabe per Wert vs. ReferenzDie Funktion new()Garbage Collection in GoRückblick – Verkettete Liste erstellen6Polymorphismus in Go
Polymorphismus über InterfacesDuck-Typing in GoRegeln zur Interface-ErfüllungPolymorphe SammlungenDependency InjectionZusammenfassung – Zahlungsprozessor9Konkurrenz & OOP
Grundlagen der GoroutinesChannels & KommunikationGepufferte vs. ungepufferte ChannelsSelect-Anweisungsync.Mutex & sync.RWMutexsync.WaitGroupThread-sicheres Struct-DesignRückblick – Worker PoolÜbe selbstständig: Online-Go-Compiler