Modello Observer
Fa parte della sezione Programmazione orientata agli oggetti del percorso GO di Coddy. Lezione 89 di 107.
Il pattern Observer definisce una relazione uno-a-molti tra oggetti. Quando un oggetto (il soggetto) cambia stato, tutti i suoi dipendenti (gli osservatori) vengono notificati automaticamente. È l'ideale per i sistemi di eventi, le notifiche o qualsiasi scenario in cui più componenti devono reagire ai cambiamenti.
In Go, implementiamo questo schema usando un'interfaccia per gli osservatori e una struttura subject che gestisce le sottoscrizioni:
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)
}
}Qualsiasi tipo che implementa l’interfaccia Observer può iscriversi per ricevere notifiche:
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)
}Il soggetto notifica tutti gli osservatori senza conoscere i loro tipi concreti:
subject := &Subject{}
subject.Subscribe(EmailAlert{Address: "user@example.com"})
subject.Subscribe(Logger{})
subject.Notify("Order placed")
// Email a user@example.com: Ordine effettuato
// Registro: Ordine effettuatoQuesto schema disaccoppia il soggetto dai suoi osservatori, facilitando l'aggiunta di nuovi tipi di osservatori senza modificare il codice esistente. È comunemente usato nelle architetture basate sugli eventi, nei framework GUI e nei sistemi di messaggistica pub/sub.
Sfida
FacileCreiamo un sistema di avvisi sui prezzi delle azioni usando il pattern Observer! Creerai un sistema in cui più servizi di avviso ricevono automaticamente notifiche ogni volta che cambia il prezzo di un'azione: l'ideale per dimostrare come gli observer possono reagire agli eventi senza che il subject conosca i loro tipi concreti.
Organizzerai il codice in tre file:
observer.go: definisci l'interfaccia observer e i tipi concreti di observer.Crea un'interfaccia
Observercon un metodoUpdate(stockName string, price float64)che riceve le informazioni sull'azione quando i prezzi cambiano.Implementa due tipi di observer:
PriceAlertcon un campoThreshold(float64); quando viene aggiornato, restituisceALERT: [stockName] at $[price] crossed threshold $[threshold]se il prezzo supera la soglia, oppureWATCHING: [stockName] at $[price] (threshold: $[threshold])se è inferiorePriceLoggercon un campoLogName(string); quando viene aggiornato, restituisce[logName] recorded [stockName]: $[price]
Entrambi gli observer devono avere un metodo che restituisca la propria stringa di risposta formattata.
stock.go: crea il subject che gestisce gli observer e li avvisa delle variazioni di prezzo.Crea una struct
Stockcon i campiName(string),Price(float64) e una slice di observer. Aggiungi questi metodi:Subscribe(o Observer)per aggiungere un observerSetPrice(price float64) []stringche aggiorna il prezzo e avvisa tutti gli observer, restituendo una slice di tutte le loro risposte
Crea anche un costruttore
NewStock(name string, initialPrice float64) *Stock.main.go: configura l'azione con gli observer e simula le variazioni di prezzo.Leggi il nome dell'azione e il prezzo iniziale. Poi leggi il numero di observer da creare. Per ciascun observer, leggi il suo tipo (
alertologger) e la sua configurazione (la soglia per gli avvisi, il nome del log per i logger). Iscrivili tutti all'azione.Infine, leggi un nuovo prezzo, aggiorna l'azione e stampa la risposta di ciascun observer su una riga separata.
Verranno forniti i seguenti input:
- Riga 1: nome dell'azione
- Riga 2: prezzo iniziale
- Riga 3: numero di observer
- Per ciascun observer: tipo (
alertologger), poi valore di configurazione - Riga finale: nuovo prezzo da impostare
Formatta tutti i prezzi con due cifre decimali.
Ad esempio, dato:
GOOG
150.00
3
alert
155.00
logger
TradeLog
alert
140.00
160.50L'output dovrebbe essere:
ALERT: GOOG at $160.50 crossed threshold $155.00
TradeLog recorded GOOG: $160.50
ALERT: GOOG at $160.50 crossed threshold $140.00E dato:
AAPL
180.00
2
logger
DailyLog
alert
200.00
175.25L'output dovrebbe essere:
DailyLog recorded AAPL: $175.25
WATCHING: AAPL at $175.25 (threshold: $200.00)Nota che Stock non sa se sta avvisando gli alert o i logger. Si limita a chiamare Update su ciascun observer. Questo disaccoppiamento rende facile aggiungere nuovi tipi di observer senza modificare il codice di Stock!
Provalo tu
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// Leggi il nome del titolo
stockName, _ := reader.ReadString('\n')
stockName = strings.TrimSpace(stockName)
// Leggi il prezzo iniziale
initialPriceStr, _ := reader.ReadString('\n')
initialPrice, _ := strconv.ParseFloat(strings.TrimSpace(initialPriceStr), 64)
// Leggi il numero di osservatori
numObserversStr, _ := reader.ReadString('\n')
numObservers, _ := strconv.Atoi(strings.TrimSpace(numObserversStr))
// TODO: Crea un nuovo Stock usando il costruttore NewStock
// TODO: Scorri tutti gli osservatori
for i := 0; i < numObservers; i++ {
// Leggi il tipo di osservatore ("alert" o "logger")
observerType, _ := reader.ReadString('\n')
observerType = strings.TrimSpace(observerType)
// Leggi il valore di configurazione
configValue, _ := reader.ReadString('\n')
configValue = strings.TrimSpace(configValue)
// TODO: Crea l'osservatore appropriato in base al tipo
// - Per "alert": converti configValue in una soglia float64 e crea PriceAlert
// - Per "logger": usa configValue come LogName e crea PriceLogger
// TODO: Sottoscrivi l'osservatore al titolo
}
// Leggi il nuovo prezzo
newPriceStr, _ := reader.ReadString('\n')
newPrice, _ := strconv.ParseFloat(strings.TrimSpace(newPriceStr), 64)
// TODO: Imposta il nuovo prezzo e ottieni le risposte
// TODO: Stampa ogni risposta su una riga separata
}
Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.
Tutte le lezioni di Programmazione orientata agli oggetti
1Fondamenti di OOP in Go
File esterniWorkspace e moduli GoPacchetti e importazioniNomi esportati e non esportatiIntroduzione all’OOP in GoGli struct come classiDefinire metodi sugli structReceiver con puntatore vs receiver con valoreInizializzazione degli structFunzioni costruttriciRiepilogo - Calcolatrice semplice4Interfacce
Introduzione alle interfacceImplementazione implicitaL’interfaccia come contrattoInterfaccia vuota (any)Asserzione di tipoSwitch sul tipoComposizione di interfacceInterfacce Stringer ed ErrorRipasso - Calcolatore di forme7Incapsulamento
Campi esportati e non esportatiIncapsulamento a livello di packageMetodi Getter e SetterNascondimento delle informazioni in GoRiepilogo - Registri degli studenti10Tipi generici (Go 1.18+)
Introduzione ai tipi genericiParametri di tipoVincoli di tipoStruct genericheSoluzione alternativa per i metodi genericiRiepilogo - Collezione generica13Modelli di progettazione Parte 1
Introduzione ai modelli di progettazioneModello SingletonModello FactoryModello Abstract FactoryModello ObserverModello Strategy2Approfondimento su tipi e struct
Tipi di base e compostiDefinizioni di tipi personalizzatiTag delle structStruct anonimeStruct annidateValori zero e predefinitiRipasso - Rubrica5Composizione invece dell'ereditarietà
Perché Go non ha l'ereditarietàFondamenti dell'embedding delle structPromozione dei metodiEmbedding di più structEmbedding e aggregazione a confrontoMascheramento dei metodi embeddedRiepilogo: gerarchia dei dipendenti8Gestione degli errori e OOP
L'interfaccia errorTipi di errore personalizzatiWrapping degli errori (fmt.Errorf)Errori sentinellaerrors.Is() ed errors.As()Panic, Defer e RecoverRiepilogo - Parser di file11Libreria standard e OOP
io.Reader e io.Writersort.InterfaceInterfaccia fmt.Stringerencoding/json con le structInterfaccia http.HandlerRipasso - modelli REST API14Modelli di progettazione – Parte 2
Pattern CommandPattern AdapterPattern DecoratorPattern Template MethodPattern StatePattern CompositeMiddleware come Decorator3Puntatori e memoria
Concetti base dei puntatori in GoPuntatori a structPassaggio per valore vs per riferimentoLa funzione new()Garbage collection in GoRiepilogo - Costruttore di liste concatenate6Polimorfismo in Go
Polimorfismo tramite interfacceDuck typing in GoRegole di soddisfacimento delle interfacceCollezioni polimorficheIniezione delle dipendenzeRiepilogo - Elaboratore di pagamenti9Concorrenza e OOP
Basi delle goroutineCanali e comunicazioneCanali bufferizzati vs non bufferizzatiIstruzione selectsync.Mutex e sync.RWMutexsync.WaitGroupProgettazione di struct thread-safeRiepilogo: worker poolEsercitati da solo: Compilatore Go online