Паттерн «Компоновщик»
Часть раздела Объектно-ориентированное программирование путешествия по 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Основы ООП
Внешние файлыВведение в ООПКлассы и объектыКлючевое слово selfМетодыСвойстваКонструктор (_init)Повторение: простой калькулятор4Полиморфизм
Переопределение методов: повторениеУтиная типизацияАбстрактные классы и методыПроектирование интерфейсовПовторение — калькулятор фигур7Сигналы
Что такое сигнал?Объявление и отправкаПодключение к сигналамСигналы с аргументамиПовторение — система здоровья10Паттерны проектирования. Часть 2
Паттерн «Команда»Паттерн «Адаптер»Паттерн «Декоратор»Паттерн «Шаблонный метод»Паттерн «Состояние»Паттерн «Компоновщик»2Свойства классов
Переменные экземпляра и классаСтатические функцииСеттеры свойствГеттеры свойствПриватность по соглашениюПовторение — менеджер банковского счёта5Инкапсуляция
Открытые и закрытые членыПроверка данных с помощью сеттеровСокрытие информацииСвойства только для чтенияПовторение — система учета студентов8Продвинутые концепции ООП
Композиция и наследованиеВнутренние классыУзлы как компонентыRefCounted и NodeПеречисления в классах11Проект: Управление библиотекой
Обзор проектаКлассы книг и пользователей3Наследование
Основы наследованияКлючевое слово superПереопределение методовЦепочки наследованияПроверка типов с помощью isПовторение — иерархия сотрудников6Специальные методы
Введение в виртуальные методыМетод _to_stringПользовательские итераторыПовторение: пользовательский список9Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн «Одиночка»Паттерн «Фабрика»Паттерн «Наблюдатель»Паттерн «Стратегия»Потренируйтесь самостоятельно: Онлайн-компилятор GDScript