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

النمط المركّب

جزء من قسم البرمجة كائنية التوجّه في رحلة GDScript على Coddy. الدرس 55 من 65.

ينشئ نمط Composite أشجارًا يكون فيها لكائن واحد ولمجموعة من الكائنات الأساليب نفسها، لذا يمكن للكود التعامل مع مجموعة كاملة كما لو كانت عنصرًا واحدًا. تحتوي الحقيبة على عناصر، ويمكن للحقيبة أن تحتوي على حقائب أخرى، ويمكنك أن تسأل أيًّا منها عن وزنها بالطريقة نفسها.

يشترك كلا النوعين في صنف أب مجرّد:

# entry.gd
@abstract
class_name Entry
extends RefCounted

var entry_name: String

@abstract func get_weight() -> int
@abstract func display(indent: String) -> void

العنصر الطرفي هو عنصر واحد:

# item.gd
class_name Item
extends Entry

var weight: int

func _init(n: String, w: int) -> void:
	entry_name = n
	weight = w

func get_weight() -> int:
	return weight

func display(indent: String) -> void:
	print(indent, entry_name, " (", weight, ")")

يحتوي المركّب على إدخالات أخرى، ويجيب عن طريق سؤال كلٍّ منها. يزن الكيس 1 بحد ذاته:

# bag.gd
class_name Bag
extends Entry

var contents: Array[Entry] = []

func _init(n: String) -> void:
	entry_name = n

func add(entry: Entry) -> void:
	contents.append(entry)

func get_weight() -> int:
	var total := 1
	for entry in contents:
		total += entry.get_weight()
	return total

func display(indent: String) -> void:
	print(indent, entry_name, " [", get_weight(), "]")
	for entry in contents:
		entry.display(indent + "  ")
# main.gd
var pouch := Bag.new("Pouch")
pouch.add(Item.new("Gem", 2))
var backpack := Bag.new("Backpack")
backpack.add(Item.new("Sword", 8))
backpack.add(pouch)
backpack.display("")
Backpack [12]
  Sword (8)
  Pouch [3]
    Gem (2)

لا تتحقق Bag.get_weight() مطلقًا مما إذا كان المُدخل عنصرًا أم حقيبة؛ فهي تستدعي الطريقة نفسها على كلٍّ منهما، والحقيبة الموجودة داخلها تجيب عن طريق الاستعلام عن محتوياتها. يتنقل هذا الاستدعاء الذاتي عبر الشجرة بأكملها، مهما بلغ عمقها.

تسير الروابط في اتجاه واحد، من الحقيبة إلى محتوياتها. إذا خزّن كل إدخال أيضًا الحقيبة التي يوجد فيها، فسيشير كلٌّ من الحقيبة وإدخالاتها إلى الآخر، مكوّنين دورة مراجع لا تُحرَّر أبدًا. عندما يحتاج إدخال إلى معرفة موقعه، مرّر هذه المعلومة إليه كوسيط، بالطريقة التي تمرّر بها display() مستوى الإزاحة.

challenge icon

التحدي

متوسط

أنشئ شجرة مجلدات باستخدام نمط المركب. يعرّف DiskEntry (disk_entry.gd) الدوال get_size() وcount_files() وlist_lines(indent). اكتب ما يلي:

  • FileEntry: حجمه هو size الخاص به، ويُحتسب ملفًا واحدًا 1، وتطبع list_lines() المسافة البادئة، ثم NAME (SIZE KB).
  • FolderEntry: تخزّن add(entry) عنصرًا فرعيًا. حجمه هو مجموع أحجام العناصر الفرعية، وعدد ملفاته هو مجموع أعداد ملفاتها، وتطبع list_lines() المسافة البادئة، ثم NAME/ (SIZE KB)، ثم تسرد كل عنصر فرعي مع إضافة مسافتين إلى المسافة البادئة.

تنشئ الشيفرة المتوفرة الشجرة من الإدخال (عدد الأسطر التي تلي ذلك، ثم folder NAME أو file NAME SIZE أو end، الذي يغلق المجلد الحالي)، وتسردها وتطبع الإجماليات. بالنسبة إلى مجلد Root يحتوي على notes.txt (4 KB) ومجلد Music يحتوي على song.mp3 (300 KB)، يكون الناتج:

Root/ (304 KB)
  notes.txt (4 KB)
  Music/ (300 KB)
    song.mp3 (300 KB)
Total: 304 KB, files: 2

ضع شيفرتك في file_entry.gd وfolder_entry.gd. يحتوي main.gd على شيفرة الإدخال والإخراج المتوفرة، ولا يمكن تعديله.

جرّب بنفسك

extends Node

func _ready():
	# كود الإدخال والإخراج المقدَّم: اتركه كما هو
	var open_folders: Array[FolderEntry] = []
	var root: FolderEntry = null
	var count := int(OS.read_string_from_stdin())
	for i in count:
		var parts := OS.read_string_from_stdin().split(" ")
		match parts[0]:
			"folder":
				var folder := FolderEntry.new(parts[1])
				if open_folders.is_empty():
					root = folder
				else:
					open_folders.back().add(folder)
				open_folders.append(folder)
			"file":
				open_folders.back().add(FileEntry.new(parts[1], int(parts[2])))
			"end":
				open_folders.pop_back()
	root.list_lines("")
	print("Total: %d KB, files: %d" % [root.get_size(), root.count_files()])
quiz iconاختبر نفسك

يتضمن هذا الدرس اختبارًا قصيرًا. ابدأ الدرس للإجابة عليه وتتبّع تقدمك.

جميع دروس البرمجة كائنية التوجّه

تدرّب بنفسك: مترجم GDScript عبر الإنترنت