Wzorzec Observer
Część sekcji Programowanie obiektowe ścieżki GO w Coddy. Lekcja 89 z 107.
Wzorzec Obserwator definiuje relację jeden-do-wielu między obiektami. Gdy jeden obiekt (podmiot) zmienia stan, wszystkie jego zależności (obserwatorzy) są automatycznie powiadamiane. To idealne rozwiązanie dla systemów zdarzeń, powiadomień lub wszelkich sytuacji, w których wiele komponentów musi reagować na zmiany.
W Go implementujemy ten wzorzec za pomocą interfejsu dla obserwatorów i struktury podmiotu, która zarządza subskrypcjami:
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)
}
}Każdy typ implementujący interfejs Observer może subskrybować powiadomienia:
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)
}Podmiot powiadamia wszystkich obserwatorów, nie znając ich konkretnych typów:
subject := &Subject{}
subject.Subscribe(EmailAlert{Address: "user@example.com"})
subject.Subscribe(Logger{})
subject.Notify("Order placed")
// E-mail do user@example.com: Zamówienie złożone
// Log: Zamówienie złożoneTen wzorzec oddziela obiekt obserwowany od jego obserwatorów, dzięki czemu można łatwo dodawać nowe typy obserwatorów bez modyfikowania istniejącego kodu. Jest powszechnie stosowany w architekturach sterowanych zdarzeniami, frameworkach GUI oraz systemach komunikatów pub/sub.
Wyzwanie
ŁatwyZbudujmy system alertów cen akcji z użyciem wzorca Observer! Utworzysz system, w którym wiele usług alertów automatycznie otrzymuje powiadomienia za każdym razem, gdy zmieni się cena akcji — to doskonały przykład tego, jak obserwatorzy mogą reagować na zdarzenia bez znajomości konkretnych typów podmiotu.
Podzielisz kod na trzy pliki:
observer.go: Zdefiniuj interfejs obserwatora i konkretne typy obserwatorów.Utwórz interfejs
Observerz metodąUpdate(stockName string, price float64), która otrzymuje informacje o akcjach po zmianie cen.Zaimplementuj dwa typy obserwatorów:
PriceAlertz polemThreshold(float64); po aktualizacji zwracaALERT: [stockName] at $[price] crossed threshold $[threshold], jeśli cena przekracza próg, alboWATCHING: [stockName] at $[price] (threshold: $[threshold]), jeśli jest poniżej niegoPriceLoggerz polemLogName(string); po aktualizacji zwraca[logName] recorded [stockName]: $[price]
Oba obserwatory powinny mieć metodę zwracającą sformatowany ciąg odpowiedzi.
stock.go: Utwórz podmiot, który zarządza obserwatorami i powiadamia ich o zmianach cen.Zbuduj strukturę
Stockz polamiName(string),Price(float64) oraz tablicą obserwatorów. Dodaj następujące metody:Subscribe(o Observer), aby dodać obserwatoraSetPrice(price float64) []string, która aktualizuje cenę i powiadamia wszystkich obserwatorów, zwracając tablicę wszystkich ich odpowiedzi
Utwórz również konstruktor
NewStock(name string, initialPrice float64) *Stock.main.go: Skonfiguruj akcje z obserwatorami i zasymuluj zmiany cen.Wczytaj nazwę akcji i cenę początkową. Następnie wczytaj liczbę obserwatorów do utworzenia. Dla każdego obserwatora wczytaj jego typ (
alertlublogger) oraz konfigurację (próg dla alertów, nazwa dziennika dla rejestratorów). Zasubskrybuj każdego obserwatora dla akcji.Na koniec wczytaj nową cenę, zaktualizuj akcje i wypisz odpowiedź każdego obserwatora w osobnym wierszu.
Dane wejściowe będą miały następujący format:
- Wiersz 1: Nazwa akcji
- Wiersz 2: Cena początkowa
- Wiersz 3: Liczba obserwatorów
- Dla każdego obserwatora: typ (
alertlublogger), a następnie wartość konfiguracji - Ostatni wiersz: Nowa cena do ustawienia
Formatuj wszystkie ceny z dokładnością do dwóch miejsc po przecinku.
Na przykład dla danych:
GOOG
150.00
3
alert
155.00
logger
TradeLog
alert
140.00
160.50Wynik powinien wyglądać następująco:
ALERT: GOOG at $160.50 crossed threshold $155.00
TradeLog recorded GOOG: $160.50
ALERT: GOOG at $160.50 crossed threshold $140.00A dla danych:
AAPL
180.00
2
logger
DailyLog
alert
200.00
175.25Wynik powinien wyglądać następująco:
DailyLog recorded AAPL: $175.25
WATCHING: AAPL at $175.25 (threshold: $200.00)Zauważ, że Stock nie wie, czy powiadamia alerty, czy rejestratory. Po prostu wywołuje Update dla każdego obserwatora. To rozdzielenie ułatwia dodawanie nowych typów obserwatorów bez modyfikowania kodu Stock!
Spróbuj swoich sił
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Odczytaj nazwę akcji
stockName, _ := reader.ReadString('\n')
stockName = strings.TrimSpace(stockName)
// Odczytaj cenę początkową
initialPriceStr, _ := reader.ReadString('\n')
initialPrice, _ := strconv.ParseFloat(strings.TrimSpace(initialPriceStr), 64)
// Odczytaj liczbę obserwatorów
numObserversStr, _ := reader.ReadString('\n')
numObservers, _ := strconv.Atoi(strings.TrimSpace(numObserversStr))
// TODO: Utwórz nowy obiekt Stock za pomocą konstruktora NewStock
// TODO: Przejdź przez wszystkich obserwatorów
for i := 0; i < numObservers; i++ {
// Odczytaj typ obserwatora ("alert" lub "logger")
observerType, _ := reader.ReadString('\n')
observerType = strings.TrimSpace(observerType)
// Odczytaj wartość konfiguracyjną
configValue, _ := reader.ReadString('\n')
configValue = strings.TrimSpace(configValue)
// TODO: Utwórz odpowiedniego obserwatora na podstawie typu
// - Dla "alert": zinterpretuj configValue jako próg typu float64 i utwórz PriceAlert
// - Dla "logger": użyj configValue jako LogName i utwórz PriceLogger
// TODO: Zasubskrybuj obserwatora do akcji
}
// Odczytaj nową cenę
newPriceStr, _ := reader.ReadString('\n')
newPrice, _ := strconv.ParseFloat(strings.TrimSpace(newPriceStr), 64)
// TODO: Ustaw nową cenę i pobierz odpowiedzi
// TODO: Wypisz każdą odpowiedź w osobnym wierszu
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy OOP w Go
Pliki zewnętrzneObszar roboczy i moduły GoPakiety i importyNazwy eksportowane i nieeksportowaneWprowadzenie do OOP w GoStruktury jako klasyDefiniowanie metod dla strukturOdbiorniki wskaźnikowe i wartościoweInicjalizacja strukturFunkcje konstruktoraPodsumowanie – prosty kalkulator4Interfejsy
Wprowadzenie do interfejsówImplementacja niejawnaInterfejs jako kontraktPusty interfejs (any)Asercja typuPrzełącznik typówKompozycja interfejsówInterfejsy Stringer i ErrorPowtórka — kalkulator figur7Hermetyzacja
Pola eksportowane i nieeksportowaneHermetyzacja na poziomie pakietuMetody pobierające i ustawiająceUkrywanie informacji w GoPowtórka – dane studentów10Typy generyczne (Go 1.18+)
Wprowadzenie do typów generycznychParametry typówOgraniczenia typówStruktury generyczneObejście ograniczeń metod generycznychPodsumowanie — generyczna kolekcja13Wzorce projektowe — część 1
Wprowadzenie do wzorców projektowychWzorzec SingletonWzorzec FactoryWzorzec Abstract FactoryWzorzec ObserverWzorzec Strategy2Typy i struktury w praktyce
Typy proste i złożoneDefiniowanie własnych typówTagi pól strukturStruktury anonimoweStruktury zagnieżdżoneWartości zerowe i domyślnePodsumowanie – książka kontaktów5Kompozycja zamiast dziedziczenia
Dlaczego Go nie ma dziedziczeniaPodstawy osadzania strukturPromowanie metodOsadzanie wielu strukturOsadzanie a agregacjaPrzesłanianie osadzonych metodPodsumowanie — hierarchia pracowników8Obsługa błędów i programowanie obiektowe
Interfejs błęduWłasne typy błędówOpakowywanie błędów (fmt.Errorf)Błędy wartowniczeerrors.Is() i errors.As()Panic, Defer i RecoverPodsumowanie — parser plików3Wskaźniki i pamięć
Podstawy wskaźników w GoWskaźniki do strukturPrzekazywanie przez wartość a przez referencjęFunkcja new()Odśmiecanie pamięci w GoPowtórka — kreator listy wiązanej6Polimorfizm w Go
Polimorfizm za pomocą interfejsówDuck typing w GoZasady spełniania interfejsówKolekcje polimorficzneWstrzykiwanie zależnościPowtórka — procesor płatności9Współbieżność i OOP
Podstawy goroutinesKanały i komunikacjaKanały buforowane i niebuforowaneInstrukcja selectsync.Mutex i sync.RWMutexsync.WaitGroupProjektowanie struktur bezpiecznych wątkowoPowtórka – pula workerów12Zaawansowane zagadnienia programowania obiektowego
Wzorzec opcji funkcyjnychWzorzec Builder w GoŁańcuchowe wywoływanie metodAliasy typów a definicje typówPodstawy refleksjiPrzegląd generowania koduPoćwicz samodzielnie: Kompilator Go online