Wzorzec Observer
Część sekcji Programowanie obiektowe ścieżki GDScript w Coddy. Lekcja 48 z 65.
Wzorzec Observer pozwala jednemu obiektowi, podmiotowi, poinformować wiele innych obiektów, obserwatorów, że coś się wydarzyło. Podmiot nie wie, co obserwatorzy zrobią z tą wiadomością; jedynie ją ogłasza.
Napisany ręcznie podmiot przechowuje tablicę obserwatorów i wywołuje tę samą metodę na każdym z nich:
# 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 ma ten wzorzec wbudowany. Sygnał jest komunikatem nadawanym przez obiekt, a każde connect() dodaje obserwatora, więc tablica i pętla nie są już potrzebne:
# 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)Obserwatorem jest dowolny obiekt, który ma metodę z parametrami pasującymi do sygnału:
# 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 nie ma pojęcia, że istnieje pasek ani dziennik. Efekt dźwiękowy lub osiągnięcie też mogą nasłuchiwać, bez wprowadzania choćby jednej zmiany w health.gd.
Trzy zasady sprawiają, że obserwatorzy działają:
- Podłączenie tej samej metody dwa razy jest błędem. Jeśli może do tego dojść, najpierw sprawdź za pomocą
hp_changed.is_connected(bar.on_hp_changed).disconnect()usuwa obserwatora, a odłączenie obserwatora, który nie jest podłączony, również jest błędem. - Połączenie nie utrzymuje obserwatora przy życiu. Przechowuj obserwatora w zmiennej lub tablicy: obiekt utworzony wewnątrz wywołania
connect()jest natychmiast zwalniany i nigdy nie odbiera sygnału. - Podłączaj metody obserwatorów. Lambda, która używa podmiotu i jest podłączona do własnego sygnału podmiotu, utrzymuje podmiot przy życiu na zawsze, co Godot zgłasza jako wyciek przy zamykaniu gry.
Wyzwanie
ŚredniZbuduj stację pogodową, która powiadamia swoje wyświetlacze za pomocą sygnału.
W pliku weather_station.gd:
- Zadeklaruj sygnał
temperature_changed(value: float). attach(display)łączy wyświetlacz z metodąon_temperature_changed. Ponowne dołączenie już dołączonego wyświetlacza nic nie robi.detach(display)rozłącza wyświetlacz. Odłączenie wyświetlacza, który nie jest dołączony, nic nie robi.set_temperature(value)zapisuje wartość wtemperaturei emituje sygnał z tą wartością.
W pliku weather_display.gd metoda on_temperature_changed(value) wypisuje nazwę wyświetlacza i wartość z jednym miejscem po przecinku, na przykład Phone: 21.5 C.
Dostarczony kod odczytuje nazwy wyświetlaczy z jednego wiersza, liczbę kolejnych poleceń, a następnie po jednym poleceniu w każdym wierszu: attach NAME, detach NAME lub temp VALUE. Na końcu wypisuje ostatnią temperaturę. Dla nazw Phone Tablet i poleceń attach Phone, attach Tablet, temp 21.5, detach Phone, temp 19 wynik to:
Phone: 21.5 C
Tablet: 21.5 C
Tablet: 19.0 C
Final temperature: 19.0Twój kod umieść w plikach weather_station.gd i weather_display.gd. Plik main.gd zawiera dostarczony kod wejścia/wyjścia i nie można go edytować.
Spróbuj swoich sił
extends Node
func _ready():
# Dostarczony kod wejścia/wyjścia: pozostaw go bez zmian
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)
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania obiektowego
Pliki zewnętrzneWprowadzenie do programowania obiektowegoKlasy a obiektySłowo kluczowe selfMetodyWłaściwościKonstruktor (_init)Powtórka – prosty kalkulator4Polimorfizm
Ponowne omówienie przesłaniania metodDuck typingKlasy i metody abstrakcyjneProjektowanie interfejsówPowtórzenie – kalkulator kształtów7Sygnały
Czym jest sygnał?Deklarowanie i emitowanieŁączenie z sygnałamiSygnały z argumentamiPodsumowanie — system zdrowia10Wzorce projektowe, część 2
Wzorzec poleceniaWzorzec adapteraWzorzec dekoratoraWzorzec metody szablonowejWzorzec stanuWzorzec kompozytu2Właściwości klas
Zmienne instancji a zmienne statyczneFunkcje statyczneSettery właściwościGettery właściwościPrywatne z konwencjiPodsumowanie — menedżer kont bankowych5Enkapsulacja
Elementy publiczne i prywatneWalidacja za pomocą setterówUkrywanie informacjiWłaściwości tylko do odczytuPodsumowanie – system ewidencji uczniów8Zaawansowane koncepcje OOP
Kompozycja a dziedziczenieKlasy wewnętrzneWęzły jako komponentyRefCounted a NodeTypy wyliczeniowe w klasach11Projekt: Zarządzanie biblioteką
Przegląd projektuKlasy książki i użytkownika3Dziedziczenie
Podstawy dziedziczeniaSłowo kluczowe superNadpisywanie metodŁańcuchy dziedziczeniaSprawdzanie typów za pomocą isPodsumowanie – hierarchia pracowników6Metody specjalne
Wprowadzenie do metod wirtualnychMetoda _to_stringWłasne iteratoryPodsumowanie — własna lista9Wzorce projektowe — część 1
Wprowadzenie do wzorców projektowychWzorzec SingletonWzorzec FactoryWzorzec ObserverWzorzec StrategyPoćwicz samodzielnie: Kompilator GDScript online