Menu
CoddyTech

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.
challenge icon

Sfida

Medio

Crea 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 metodo on_temperature_changed del 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 in temperature ed 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.0

Il 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)
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 GDScript online