Menu
CoddyTech

Kompositum-Muster

Teil des Abschnitts Objektorientierte Programmierung der GDScript-Journey von Coddy. Lektion 55 von 65.

Das Composite-Muster erstellt Bäume, in denen ein einzelnes Objekt und eine Gruppe von Objekten dieselben Methoden haben, sodass Code eine ganze Gruppe wie ein einzelnes Element behandeln kann. Eine Tasche enthält Elemente, eine Tasche kann andere Taschen enthalten, und du kannst jede von ihnen auf dieselbe Weise nach ihrem Gewicht fragen.

Beide Arten haben eine abstrakte Basisklasse:

# entry.gd
@abstract
class_name Entry
extends RefCounted

var entry_name: String

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

Das Blatt ist ein einzelnes Element:

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

Das zusammengesetzte Objekt enthält weitere Einträge und fragt jeden von ihnen ab. Eine Tasche wiegt selbst 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() prüft nie, ob ein Eintrag ein Gegenstand oder eine Tasche ist: Es ruft bei jedem dieselbe Methode auf, und eine Tasche darin antwortet, indem sie ihren eigenen Inhalt abfragt. Diese Rekursion durchläuft den gesamten Baum, egal wie tief er ist.

Verknüpfungen verlaufen in eine Richtung, von einer Tasche zu ihrem Inhalt. Wenn jeder Eintrag auch die Tasche speichern würde, in der er sich befindet, würden jede Tasche und ihre Einträge aufeinander verweisen – ein Referenzzyklus, der nie freigegeben wird. Wenn ein Eintrag wissen muss, wo er sich befindet, übergib diese Information als Argument, so wie display() die Einrückung übergibt.

challenge icon

Aufgabe

Mittel

Erstelle mit dem Composite-Muster einen Ordnerbaum. DiskEntry (disk_entry.gd) deklariert get_size(), count_files() und list_lines(indent). Schreibe:

  • FileEntry: Seine Größe ist sein eigenes size, es zählt als 1 Datei, und list_lines() gibt die Einrückung und dann NAME (SIZE KB) aus.
  • FolderEntry: add(entry) speichert ein untergeordnetes Element. Seine Größe ist die Summe der Größen seiner untergeordneten Elemente, seine Dateianzahl die Summe ihrer Dateianzahlen, und list_lines() gibt die Einrückung und dann NAME/ (SIZE KB) aus und listet anschließend jedes untergeordnete Element mit der Einrückung plus zwei Leerzeichen auf.

Der bereitgestellte Code erstellt den Baum aus der Eingabe (wie viele Zeilen folgen, dann folder NAME, file NAME SIZE oder end, wodurch der aktuelle Ordner geschlossen wird), listet ihn auf und gibt die Gesamtwerte aus. Für einen Root-Ordner mit notes.txt (4 KB) und einem Music-Ordner mit song.mp3 (300 KB) lautet die Ausgabe:

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

Dein Code gehört in file_entry.gd und folder_entry.gd. main.gd enthält den bereitgestellten Ein-/Ausgabecode und kann nicht bearbeitet werden.

Probier es selbst

extends Node

func _ready():
	# Vorgegebener Eingabe-/Ausgabecode: unverändert lassen
	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 iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung

Übe selbstständig: Online-GDScript-Compiler