Pattern Observer
Fa parte della sezione Programmazione orientata agli oggetti del percorso GDScript di Coddy. Lezione 48 di 65.
Il pattern Observer permette a un oggetto, il soggetto, di comunicare a molti altri oggetti, gli osservatori, che è successo qualcosa. Il soggetto non sa cosa facciano gli osservatori con la notizia; si limita ad annunciarla.
Scritto a mano, il soggetto mantiene un array di osservatori e chiama lo stesso metodo su ciascuno:
# health.gd
class_name Health
extends RefCounted
var observers: Array = []
var hp := 100
func add_observer(observer) -> void:
observers.append(observer)
func take_damage(amount: int) -> void:
hp -= amount
for observer in observers:
observer.on_hp_changed(hp)Godot ha questo schema integrato. Un segnale è l'annuncio del soggetto e ogni connect() aggiunge un osservatore, quindi l'array e il ciclo non sono più necessari:
# health.gd
class_name Health
extends RefCounted
signal hp_changed(new_hp: int)
var hp := 100
func take_damage(amount: int) -> void:
hp -= amount
hp_changed.emit(hp)Un osservatore è qualsiasi oggetto con un metodo i cui parametri corrispondono al segnale:
# health_bar.gd
class_name HealthBar
extends RefCounted
func on_hp_changed(new_hp: int) -> void:
print("Bar: ", new_hp, "/100")# damage_log.gd
class_name DamageLog
extends RefCounted
func on_hp_changed(new_hp: int) -> void:
if new_hp < 50:
print("Log: low health!")# main.gd
var health := Health.new()
var bar := HealthBar.new()
var damage_log := DamageLog.new()
health.hp_changed.connect(bar.on_hp_changed)
health.hp_changed.connect(damage_log.on_hp_changed)
health.take_damage(70)Bar: 30/100
Log: low health!Health non ha idea che esistano una barra o un registro. Anche un effetto sonoro o un obiettivo possono ascoltare, senza apportare alcuna modifica a health.gd.
Tre regole mantengono funzionanti gli osservatori:
- Collegare lo stesso metodo due volte è un errore. Quando potrebbe succedere, verifica prima con
hp_changed.is_connected(bar.on_hp_changed).disconnect()rimuove un osservatore e anche scollegarne uno che non è collegato è un errore. - Una connessione non mantiene in vita l'osservatore. Conserva l'osservatore in una variabile o in un array: un oggetto creato all'interno della chiamata
connect()viene liberato immediatamente e non riceve mai il segnale. - Collega i metodi degli osservatori. Una lambda che usa il soggetto ed è collegata al segnale del soggetto stesso mantiene in vita il soggetto per sempre, e Godot lo segnala come perdita di memoria quando il gioco si chiude.
Sfida
MedioCrea una stazione meteorologica che notifichi i suoi display tramite un segnale.
In weather_station.gd:
- Dichiara il segnale
temperature_changed(value: float). attach(display)connette il metodoon_temperature_changeddel display. Se colleghi un display già collegato, non succede nulla.detach(display)lo disconnette. Se scolleghi un display non collegato, non succede nulla.set_temperature(value)memorizza il valore intemperatureed emette il segnale con quel valore.
In weather_display.gd, on_temperature_changed(value) stampa il nome del display e il valore con una cifra decimale, come Phone: 21.5 C.
Il codice fornito legge i nomi dei display su una riga, il numero di comandi successivi e poi un comando per riga: attach NAME, detach NAME oppure temp VALUE. Alla fine stampa l'ultima temperatura. Per i nomi Phone Tablet e i comandi attach Phone, attach Tablet, temp 21.5, detach Phone, temp 19, l'output è:
Phone: 21.5 C
Tablet: 21.5 C
Tablet: 19.0 C
Final temperature: 19.0Il tuo codice va in weather_station.gd e weather_display.gd. main.gd contiene il codice fornito per l'input/output e non può essere modificato.
Provalo tu
extends Node
func _ready():
# Codice di input/output fornito: lascialo così com'è
var station := WeatherStation.new()
var displays := {}
for display_name in OS.read_string_from_stdin().split(" "):
displays[display_name] = WeatherDisplay.new(display_name)
var count := int(OS.read_string_from_stdin())
for i in count:
var parts := OS.read_string_from_stdin().split(" ")
match parts[0]:
"attach":
station.attach(displays[parts[1]])
"detach":
station.detach(displays[parts[1]])
"temp":
station.set_temperature(float(parts[1]))
print("Final temperature: %.1f" % station.temperature)
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 della programmazione orientata agli oggetti
File esterniIntroduzione alla programmazione orientata agli oggettiClassi e oggetti a confrontoLa parola chiave selfMetodiProprietàCostruttore (_init)Riepilogo - Calcolatrice semplice4Polimorfismo
Ripasso dell'override dei metodiDuck typingClassi e metodi astrattiProgettazione delle interfacceRiepilogo - Calcolatore di forme7Segnali
Che cos'è un segnale?Dichiarare ed emettereCollegarsi ai segnaliSegnali con argomentiRipasso - Sistema della salute10Pattern di progettazione, parte 2
Pattern CommandPattern AdapterPattern DecoratorPattern Template MethodPattern StatePattern Composite2Proprietà delle classi
Variabili di istanza e staticheFunzioni staticheSetter delle proprietàGetter delle proprietàPrivato per convenzioneRiepilogo - Gestore di conti bancari5Incapsulamento
Membri pubblici e privatiValidazione con i setterNascondimento delle informazioniProprietà di sola letturaRiepilogo - Sistema di gestione dei dati degli studenti8Concetti avanzati di OOP
Composizione vs ereditarietàClassi interneNodi come componentiRefCounted vs NodeEnum nelle classi11Progetto: Gestione della biblioteca
Panoramica del progettoClassi Libro e Utente3Ereditarietà
Ereditarietà di baseLa parola chiave superOverride dei metodiCatene di ereditarietàControlli di tipo con isRipasso - Gerarchia dei dipendenti6Metodi speciali
Introduzione ai metodi virtualiIl metodo _to_stringIteratori personalizzatiRiepilogo - Lista personalizzata9Modelli di progettazione, parte 1
Introduzione ai modelli di progettazionePattern SingletonPattern FactoryPattern ObserverPattern StrategyEsercitati da solo: Compilatore GDScript online