Menu
Coddy logo textTech

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żone

Ten 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.

challenge icon

Wyzwanie

Łatwy

Zbudujmy 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 Observer z metodą Update(stockName string, price float64), która otrzymuje informacje o akcjach po zmianie cen.

    Zaimplementuj dwa typy obserwatorów:

    • PriceAlert z polem Threshold (float64); po aktualizacji zwraca ALERT: [stockName] at $[price] crossed threshold $[threshold], jeśli cena przekracza próg, albo WATCHING: [stockName] at $[price] (threshold: $[threshold]), jeśli jest poniżej niego
    • PriceLogger z polem LogName (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ę Stock z polami Name (string), Price (float64) oraz tablicą obserwatorów. Dodaj następujące metody:

    • Subscribe(o Observer), aby dodać obserwatora
    • SetPrice(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 (alert lub logger) 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 (alert lub logger), 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.50

Wynik 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.00

A dla danych:

AAPL
180.00
2
logger
DailyLog
alert
200.00
175.25

Wynik 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
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator Go online