Menu
CoddyTech

תבנית Observer

חלק מהיחידה תכנות מונחה עצמים במסלול ה-GDScript של Coddy. שיעור 48 מתוך 65.

תבנית Observer מאפשרת לאובייקט אחד, הנושא, להודיע לאובייקטים רבים אחרים, הצופים, שמשהו קרה. הנושא אינו יודע מה הצופים עושים עם המידע; הוא רק מודיע עליו.

בכתיבה ידנית, הנושא מחזיק מערך של משקיפים וקורא לאותה מתודה בכל אחד מהם:

# 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. אות הוא ההכרזה של הנושא, וכל connect() מוסיף צופה, כך שאין עוד צורך במערך ובלולאה:

# 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)

צופה הוא כל אובייקט שיש לו מתודה שהפרמטרים שלה תואמים לאות:

# 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 אין מושג שקיימים מד או יומן. גם אפקט צליל או הישג יכולים להאזין, בלי לשנות אפילו דבר אחד ב־health.gd.

שלושה כללים שומרים על כך שהמאזינים יפעלו:

  • חיבור של אותה שיטה פעמיים הוא שגיאה. כשזה עלול לקרות, בדקו תחילה באמצעות hp_changed.is_connected(bar.on_hp_changed). הפעולה disconnect() מסירה מאזין, וגם ניתוק של מאזין שאינו מחובר הוא שגיאה.
  • חיבור אינו משאיר את המאזין בחיים. שמרו את המאזין במשתנה או במערך: אובייקט שנוצר בתוך הקריאה ל-connect() משתחרר מיד ואינו שומע את האות לעולם.
  • חברו שיטות של המאזינים. למבדה שמשתמשת בנושא ומחוברת לאות של הנושא עצמו משאירה את הנושא בחיים לנצח, ו-Godot מדווח על כך כדליפת זיכרון כשהמשחק נסגר.
challenge icon

אתגר

בינוני

בנו תחנת מזג אוויר שמודיעה לצגים שלה באמצעות אות.

בקובץ weather_station.gd:

  • הצהירו על האות temperature_changed(value: float).
  • attach(display) מחברת את שיטת on_temperature_changed של הצג. צירוף של צג שכבר מחובר לא עושה דבר.
  • detach(display) מנתקת אותו. ניתוק של צג שאינו מחובר לא עושה דבר.
  • set_temperature(value) שומרת את הערך ב-temperature ופולטת את האות עם הערך הזה.

בקובץ weather_display.gd, השיטה on_temperature_changed(value) מדפיסה את שם הצג ואת הערך עם ספרה אחת אחרי הנקודה, כמו Phone: 21.5 C.

הקוד שסופק קורא את שמות הצגים בשורה אחת, אחר כך את מספר הפקודות שיבואו, ואז פקודה אחת בכל שורה: attach NAME, detach NAME או temp VALUE. בסוף הוא מדפיס את הטמפרטורה האחרונה. עבור השמות Phone Tablet והפקודות attach Phone, attach Tablet, temp 21.5, detach Phone, temp 19 הפלט הוא:

Phone: 21.5 C
Tablet: 21.5 C
Tablet: 19.0 C
Final temperature: 19.0

הקוד שלכם יופיע בקבצים weather_station.gd ו-weather_display.gd. הקובץ main.gd מכיל את קוד הקלט/פלט שסופק, ואי אפשר לערוך אותו.

נסו בעצמכם

extends Node

func _ready():
	# קוד הקלט/פלט שסופק: יש להשאיר אותו כפי שהוא
	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 iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה תכנות מונחה עצמים

תרגלו בעצמכם: קומפיילר GDScript אונליין