Patron Observer
Fait partie de la section Programmation orientée objet du Journey GDScript de Coddy. Leçon 48 sur 65.
Le modèle Observer permet à un objet, le sujet, d’informer de nombreux autres objets, les observateurs, qu’un événement s’est produit. Le sujet ne sait pas ce que les observateurs font de cette information ; il se contente de l’annoncer.
Écrit à la main, le sujet conserve un tableau d’observateurs et appelle la même méthode sur chacun d’eux :
# 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 intègre ce modèle. Un signal est l’annonce du sujet, et chaque connect() ajoute un observateur, de sorte que le tableau et la boucle ne sont plus nécessaires :
# 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 observateur est tout objet doté d’une méthode dont les paramètres correspondent au signal :
# 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 n’a aucune idée qu’une barre ou un journal existe. Un effet sonore ou un succès peut aussi écouter, sans apporter la moindre modification à health.gd.
Trois règles permettent aux observateurs de fonctionner :
- Connecter deux fois la même méthode est une erreur. Lorsque cela peut se produire, vérifie d’abord avec
hp_changed.is_connected(bar.on_hp_changed).disconnect()supprime un observateur, et déconnecter un observateur qui n’est pas connecté est également une erreur. - Une connexion ne maintient pas l’observateur en vie. Conserve l’observateur dans une variable ou un tableau : un objet créé à l’intérieur de l’appel
connect()est immédiatement libéré et ne reçoit jamais le signal. - Connecte les méthodes des observateurs. Une lambda qui utilise le sujet et qui est connectée au propre signal du sujet maintient celui-ci en vie pour toujours, ce que Godot signale comme une fuite à la fermeture du jeu.
Défi
MoyenCrée une station météo qui informe ses afficheurs à l’aide d’un signal.
Dans weather_station.gd :
- Déclare le signal
temperature_changed(value: float). attach(display)connecte la méthodeon_temperature_changedde l’afficheur. Attacher un afficheur qui l’est déjà ne fait rien.detach(display)le déconnecte. Détacher un afficheur qui n’est pas attaché ne fait rien.set_temperature(value)stocke la valeur danstemperatureet émet le signal avec cette valeur.
Dans weather_display.gd, on_temperature_changed(value) affiche le nom de l’afficheur et la valeur avec une décimale, comme Phone: 21.5 C.
Le code fourni lit les noms des afficheurs sur une ligne, le nombre de commandes qui suivent, puis une commande par ligne : attach NAME, detach NAME ou temp VALUE. À la fin, il affiche la dernière température. Pour les noms Phone Tablet et les commandes attach Phone, attach Tablet, temp 21.5, detach Phone, temp 19, le résultat est :
Phone: 21.5 C
Tablet: 21.5 C
Tablet: 19.0 C
Final temperature: 19.0Ton code va dans weather_station.gd et weather_display.gd. main.gd contient le code d’entrée/sortie fourni et ne peut pas être modifié.
Essayez vous-même
extends Node
func _ready():
# Code d'entrée/sortie fourni : conservez-le tel quel
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)
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Programmation orientée objet
1Fondamentaux de la POO
Fichiers externesIntroduction à la POOClasses et objetsLe mot-clé selfMéthodesPropriétésConstructeur (_init)Récapitulatif - Calculatrice simple4Polymorphisme
Retour sur la redéfinition des méthodesTypage canardClasses et méthodes abstraitesConception d’interfacesRécapitulatif – Calculateur de formes7Signaux
Qu’est-ce qu’un signal ?Déclarer et émettreSe connecter aux signauxSignaux avec argumentsRécapitulatif - système de santé10Patrons de conception, partie 2
Patron CommandePatron AdaptateurPatron DécorateurPatron Méthode modèlePatron ÉtatPatron Composite2Propriétés de classe
Variables d’instance et variables statiquesFonctions statiquesSetters de propriétésGetters de propriétésPrivé par conventionRécapitulatif - Gestionnaire de comptes bancaires5Encapsulation
Membres publics et privésValidation avec les méthodes setterMasquage de l’informationPropriétés en lecture seuleRécapitulatif - Système de dossiers étudiants8Concepts avancés de la POO
Composition ou héritageClasses internesLes nœuds comme composantsRefCounted ou NodeEnums dans les classes3Héritage
Héritage de baseLe mot-clé superRedéfinition de méthodesChaînes d’héritageVérifier les types avec isRécapitulatif – Hiérarchie des employés6Méthodes spéciales
Introduction aux méthodes virtuellesLa méthode _to_stringItérateurs personnalisésRécapitulatif – Liste personnalisée9Patrons de conception, partie 1
Introduction aux patrons de conceptionPatron SingletonPatron FactoryPatron ObserverPatron StrategyEntraînez-vous par vous-même : Compilateur GDScript en ligne