Menu
Coddy logo textTech

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 effettuato

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

challenge icon

Sfida

Facile

Creiamo 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 Observer con un metodo Update(stockName string, price float64) che riceve le informazioni sull'azione quando i prezzi cambiano.

    Implementa due tipi di observer:

    • PriceAlert con un campo Threshold (float64); quando viene aggiornato, restituisce ALERT: [stockName] at $[price] crossed threshold $[threshold] se il prezzo supera la soglia, oppure WATCHING: [stockName] at $[price] (threshold: $[threshold]) se è inferiore
    • PriceLogger con un campo LogName (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 Stock con i campi Name (string), Price (float64) e una slice di observer. Aggiungi questi metodi:

    • Subscribe(o Observer) per aggiungere un observer
    • SetPrice(price float64) []string che 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 (alert o logger) 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 (alert o logger), 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.50

L'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.00

E dato:

AAPL
180.00
2
logger
DailyLog
alert
200.00
175.25

L'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
}
quiz iconMettiti alla prova

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

Esercitati da solo: Compilatore Go online