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