תבנית המורכב
חלק מהיחידה תכנות מונחה עצמים במסלול ה-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() מעבירה את ההזחה.
אתגר
בינוניבנו עץ תיקיות באמצעות תבנית 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()])
השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.
כל השיעורים ביחידה תכנות מונחה עצמים
1יסודות התכנות מונחה העצמים
קבצים חיצונייםמבוא ל-OOPמחלקות לעומת אובייקטיםמילת המפתח selfמתודותמאפייניםבנאי (_init)חזרה – מחשבון פשוט2מאפייני מחלקה
משתני מופע לעומת משתנים סטטייםפונקציות סטטיותקובעי מאפייניםמאחזרי מאפייניםפרטיים לפי מוסכמהסיכום – מנהל חשבון בנק5כימוס
איברים ציבוריים ופרטייםאימות באמצעות Settersהסתרת מידעמאפיינים לקריאה בלבדסיכום – מערכת לניהול רשומות תלמידיםתרגלו בעצמכם: קומפיילר GDScript אונליין