Menu
CoddyTech

Patrón Composite

Parte de la sección Programación orientada a objetos del Journey de GDScript de Coddy. Lección 55 de 65.

El patrón Composite construye árboles en los que un objeto individual y un grupo de objetos tienen los mismos métodos, para que el código pueda tratar a todo un grupo como un solo elemento. Una bolsa contiene elementos, una bolsa puede contener otras bolsas y puedes preguntarles a todas por su peso de la misma manera.

Ambos tipos comparten un padre abstracto:

# entry.gd
@abstract
class_name Entry
extends RefCounted

var entry_name: String

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

La hoja es un elemento individual:

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

El compuesto contiene otras entradas y responde preguntándole a cada una de ellas. Una bolsa pesa 1 por sí sola:

# 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() nunca comprueba si una entrada es un elemento o una bolsa: llama al mismo método en cada una, y una bolsa que está dentro responde preguntando a su propio contenido. Esa recursión recorre todo el árbol, por muy profundo que sea.

Los enlaces van en una sola dirección, de una carpeta a su contenido. Si cada elemento también almacenara la carpeta en la que está, cada carpeta y sus elementos se apuntarían mutuamente, formando un ciclo de referencias que nunca se libera. Cuando un elemento necesite saber dónde está, pásale esa información como argumento, de la misma manera que display() pasa la sangría.

challenge icon

Desafío

Intermedio

Crea un árbol de carpetas con el patrón Composite. DiskEntry (disk_entry.gd) declara get_size(), count_files() y list_lines(indent). Escribe:

  • FileEntry: su tamaño es su propio size, cuenta como 1 archivo y list_lines() imprime la sangría, después NAME (SIZE KB).
  • FolderEntry: add(entry) almacena un elemento hijo. Su tamaño es la suma de los tamaños de sus elementos hijos, el recuento de archivos es la suma de los suyos y list_lines() imprime la sangría, después NAME/ (SIZE KB) y luego enumera cada elemento hijo con la sangría más dos espacios.

El código proporcionado construye el árbol a partir de la entrada (cuántas líneas siguen, luego folder NAME, file NAME SIZE o end, que cierra la carpeta actual), lo enumera e imprime los totales. Para una carpeta Root que contiene notes.txt (4 KB) y una carpeta Music con song.mp3 (300 KB), la salida es:

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

Tu código va en file_entry.gd y folder_entry.gd. main.gd contiene el código proporcionado de entrada/salida y no se puede editar.

Pruébalo tú mismo

extends Node

func _ready():
	# Código de entrada/salida proporcionado: déjalo tal como está
	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 iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Programación orientada a objetos

Practica por tu cuenta: Compilador de GDScript online