Menu
CoddyTech
flag Ar iconالعربيةdown icon

نمط 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 عبر الإنترنت