Wzorzec kompozytu
Część sekcji Programowanie obiektowe ścieżki GDScript w Coddy. Lekcja 55 z 65.
Wzorzec Composite tworzy drzewa, w których pojedynczy obiekt i grupa obiektów mają te same metody, dzięki czemu kod może traktować całą grupę jak jeden element. Torba zawiera przedmioty, może zawierać inne torby, a o wagę każdej z nich pytasz w ten sam sposób.
Oba rodzaje mają wspólną klasę abstrakcyjną:
# entry.gd
@abstract
class_name Entry
extends RefCounted
var entry_name: String
@abstract func get_weight() -> int
@abstract func display(indent: String) -> voidLiść jest pojedynczym elementem:
# 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, ")")Kontener złożony przechowuje inne elementy i odpowiada, pytając każdy z nich. Sam worek waży 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() nigdy nie sprawdza, czy wpis jest przedmiotem, czy torbą: wywołuje tę samą metodę dla każdego z nich, a znajdująca się w środku torba odpowiada, pytając o własną zawartość. Ta rekurencja przechodzi przez całe drzewo, niezależnie od jego głębokości.
Łącza prowadzą tylko w jedną stronę — od torby do jej zawartości. Gdyby każdy wpis przechowywał również informację o torbie, w której się znajduje, każda torba i jej wpisy wskazywałyby na siebie nawzajem, tworząc cykl odwołań, który nigdy nie zostanie zwolniony. Gdy wpis musi wiedzieć, gdzie się znajduje, przekaż tę informację jako argument, tak jak display() przekazuje wcięcie.
Wyzwanie
ŚredniZbuduj drzewo folderów z użyciem wzorca Composite. DiskEntry (disk_entry.gd) deklaruje get_size(), count_files() i list_lines(indent). Napisz:
FileEntry: jego rozmiar to jego własnesize, jest liczony jako1plik, alist_lines()wypisuje wcięcie, a następnieNAME (SIZE KB).FolderEntry:add(entry)przechowuje element podrzędny. Jego rozmiar to suma rozmiarów elementów podrzędnych, liczba plików to suma ich liczby plików, alist_lines()wypisuje wcięcie, a następnieNAME/ (SIZE KB), po czym wyświetla każdy element podrzędny z wcięciem powiększonym o dwie spacje.
Dostarczony kod buduje drzewo na podstawie danych wejściowych (ile wierszy następuje dalej, a potem folder NAME, file NAME SIZE lub end, które zamyka bieżący folder), wyświetla je i wypisuje sumy. Dla folderu Root zawierającego notes.txt (4 KB) oraz folderu Music z plikiem song.mp3 (300 KB) wynik to:
Root/ (304 KB)
notes.txt (4 KB)
Music/ (300 KB)
song.mp3 (300 KB)
Total: 304 KB, files: 2Umieść swój kod w plikach file_entry.gd i folder_entry.gd. main.gd zawiera dostarczony kod wejścia/wyjścia i nie można go edytować.
Spróbuj swoich sił
extends Node
func _ready():
# Dostarczony kod wejścia/wyjścia: pozostaw go bez zmian
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()])
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania obiektowego
Pliki zewnętrzneWprowadzenie do programowania obiektowegoKlasy a obiektySłowo kluczowe selfMetodyWłaściwościKonstruktor (_init)Powtórka – prosty kalkulator4Polimorfizm
Ponowne omówienie przesłaniania metodDuck typingKlasy i metody abstrakcyjneProjektowanie interfejsówPowtórzenie – kalkulator kształtów7Sygnały
Czym jest sygnał?Deklarowanie i emitowanieŁączenie z sygnałamiSygnały z argumentamiPodsumowanie — system zdrowia10Wzorce projektowe, część 2
Wzorzec poleceniaWzorzec adapteraWzorzec dekoratoraWzorzec metody szablonowejWzorzec stanuWzorzec kompozytu2Właściwości klas
Zmienne instancji a zmienne statyczneFunkcje statyczneSettery właściwościGettery właściwościPrywatne z konwencjiPodsumowanie — menedżer kont bankowych5Enkapsulacja
Elementy publiczne i prywatneWalidacja za pomocą setterówUkrywanie informacjiWłaściwości tylko do odczytuPodsumowanie – system ewidencji uczniów8Zaawansowane koncepcje OOP
Kompozycja a dziedziczenieKlasy wewnętrzneWęzły jako komponentyRefCounted a NodeTypy wyliczeniowe w klasach11Projekt: Zarządzanie biblioteką
Przegląd projektuKlasy książki i użytkownika3Dziedziczenie
Podstawy dziedziczeniaSłowo kluczowe superNadpisywanie metodŁańcuchy dziedziczeniaSprawdzanie typów za pomocą isPodsumowanie – hierarchia pracowników6Metody specjalne
Wprowadzenie do metod wirtualnychMetoda _to_stringWłasne iteratoryPodsumowanie — własna lista9Wzorce projektowe — część 1
Wprowadzenie do wzorców projektowychWzorzec SingletonWzorzec FactoryWzorzec ObserverWzorzec StrategyPoćwicz samodzielnie: Kompilator GDScript online