Menu
CoddyTech

תבנית המורכב

חלק מהיחידה תכנות מונחה עצמים במסלול ה-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

אתגר

בינוני

בנו עץ תיקיות באמצעות תבנית Composite. 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 אונליין