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) -> voidLa 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.
Desafío
IntermedioCrea 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 propiosize, cuenta como1archivo ylist_lines()imprime la sangría, despuésNAME (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 ylist_lines()imprime la sangría, despuésNAME/ (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: 2Tu 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()])
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
1Fundamentos de la programación orientada a objetos
Archivos externosIntroducción a la programación orientada a objetosClases vs. objetosLa palabra clave selfMétodosPropiedadesConstructor (_init)Repaso: calculadora sencilla4Polimorfismo
Repaso de la sobrescritura de métodosTipado patoClases y métodos abstractosDiseño de interfacesRepaso: calculadora de formas7Señales
¿Qué es una señal?Declarar y emitirConectarse a las señalesSeñales con argumentosRepaso: sistema de salud10Patrones de diseño, parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón Composite2Propiedades de clase
Variables de instancia y estáticasFunciones estáticasMétodos setter de propiedadesMétodos getter de propiedadesPrivado por convenciónRepaso: administrador de cuentas bancarias5Encapsulación
Miembros públicos y privadosValidación con settersOcultación de informaciónPropiedades de solo lecturaRepaso: sistema de registros de estudiantes8Conceptos avanzados de POO
Composición frente a herenciaClases internasNodos como componentesRefCounted frente a NodeEnums en clases3Herencia
Herencia básicaLa palabra clave superSobrescritura de métodosCadenas de herenciaComprobaciones de tipo con isRepaso: jerarquía de empleados6Métodos especiales
Introducción a los métodos virtualesEl método _to_stringIteradores personalizadosRepaso: lista personalizada9Patrones de diseño, parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón ObserverPatrón StrategyPractica por tu cuenta: Compilador de GDScript online