Patrón Observer
Parte de la sección Programación orientada a objetos del Journey de GDScript de Coddy. Lección 48 de 65.
El patrón Observer permite que un objeto, el sujeto, les comunique a muchos otros objetos, los observadores, que ha ocurrido algo. El sujeto no sabe qué hacen los observadores con la noticia; solo la anuncia.
Escrito manualmente, el sujeto mantiene un arreglo de observadores y llama al mismo método en cada uno:
# 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 ya incluye este patrón. Una señal es el anuncio del sujeto, y cada connect() añade un observador, así que ya no se necesitan el array ni el bucle:
# 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 observador es cualquier objeto con un método cuyos parámetros coinciden con la señal:
# 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 no tiene ni idea de que existe una barra o un registro. Un efecto de sonido o un logro también pueden escuchar, sin cambiar ni una sola cosa en health.gd.
Tres reglas hacen que los observadores sigan funcionando:
- Conectar el mismo método dos veces es un error. Cuando pueda ocurrir, compruébalo primero con
hp_changed.is_connected(bar.on_hp_changed).disconnect()elimina un observador, y desconectar uno que no está conectado también es un error. - Una conexión no mantiene vivo al observador. Guarda el observador en una variable o en un arreglo: un objeto creado dentro de la llamada a
connect()se libera de inmediato y nunca recibe la señal. - Conecta métodos de los observadores. Una lambda que usa el sujeto y está conectada a la propia señal del sujeto lo mantiene vivo para siempre, lo que Godot informa como una fuga cuando se cierra el juego.
Desafío
IntermedioConstruye una estación meteorológica que notifique a sus pantallas mediante una señal.
En weather_station.gd:
- Declara la señal
temperature_changed(value: float). attach(display)conecta el métodoon_temperature_changedde la pantalla. Si se conecta una pantalla que ya está conectada, no hace nada.detach(display)la desconecta. Si se desconecta una pantalla que no está conectada, no hace nada.set_temperature(value)almacena el valor entemperaturey lo emite con la señal.
En weather_display.gd, on_temperature_changed(value) imprime el nombre de la pantalla y el valor con un decimal, como Phone: 21.5 C.
El código proporcionado lee los nombres de las pantallas en una línea, cuántos comandos siguen y luego un comando por línea: attach NAME, detach NAME o temp VALUE. Al final, imprime la última temperatura. Para los nombres Phone Tablet y los comandos attach Phone, attach Tablet, temp 21.5, detach Phone, temp 19, el resultado es:
Phone: 21.5 C
Tablet: 21.5 C
Tablet: 19.0 C
Final temperature: 19.0Tu código va en weather_station.gd y weather_display.gd. main.gd contiene el código de entrada/salida proporcionado y no se puede editar.
Pruébalo tú mismo
extends Node
func _ready():
# Código de entrada/salida proporcionado: mantenlo tal como está
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)
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación orientada a objetos
1Fundamentos de la programación orientada a objetos
Archivos externosIntroducción a la programación orientada a objetosClases vs. objetosLa palabra clave selfMétodosPropiedadesConstructor (_init)Repaso: calculadora sencilla4Polimorfismo
Repaso de la sobrescritura de métodosTipado patoClases y métodos abstractosDiseño de interfacesRepaso: calculadora de formas7Señales
¿Qué es una señal?Declarar y emitirConectarse a las señalesSeñales con argumentosRepaso: sistema de salud10Patrones de diseño, parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón Composite2Propiedades de clase
Variables de instancia y estáticasFunciones estáticasMétodos setter de propiedadesMétodos getter de propiedadesPrivado por convenciónRepaso: administrador de cuentas bancarias5Encapsulación
Miembros públicos y privadosValidación con settersOcultación de informaciónPropiedades de solo lecturaRepaso: sistema de registros de estudiantes8Conceptos avanzados de POO
Composición frente a herenciaClases internasNodos como componentesRefCounted frente a NodeEnums en clases3Herencia
Herencia básicaLa palabra clave superSobrescritura de métodosCadenas de herenciaComprobaciones de tipo con isRepaso: jerarquía de empleados6Métodos especiales
Introducción a los métodos virtualesEl método _to_stringIteradores personalizadosRepaso: lista personalizada9Patrones de diseño, parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón ObserverPatrón StrategyPractica por tu cuenta: Compilador de GDScript online