Patron Composite
Fait partie de la section Programmation orientée objet du Journey GDScript de Coddy. Leçon 55 sur 65.
Le modèle Composite construit des arbres dans lesquels un objet unique et un groupe d’objets ont les mêmes méthodes, afin que le code puisse traiter tout un groupe comme un seul élément. Un sac contient des éléments, un sac peut contenir d’autres sacs, et tu peux demander le poids de chacun de la même manière.
Les deux types partagent un parent abstrait :
# entry.gd
@abstract
class_name Entry
extends RefCounted
var entry_name: String
@abstract func get_weight() -> int
@abstract func display(indent: String) -> voidLa feuille est un élément unique :
# 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, ")")Le composite contient d’autres entrées et obtient des réponses en interrogeant chacune d’elles. Un sac pèse lui-même 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() ne vérifie jamais si une entrée est un objet ou un sac : elle appelle la même méthode sur chacun d’eux, et un sac imbriqué répond en interrogeant son propre contenu. Cette récursion parcourt tout l’arbre, quelle que soit sa profondeur.
Les liens vont dans un seul sens, d’un sac vers son contenu. Si chaque entrée stockait également le sac dans lequel elle se trouve, chaque sac et ses entrées pointeraient les uns vers les autres, créant un cycle de références qui n’est jamais libéré. Lorsqu’une entrée a besoin de savoir où elle se trouve, transmettez cette information en argument, comme display() transmet l’indentation.
Défi
MoyenConstruisez un arbre de dossiers avec le patron Composite. DiskEntry (disk_entry.gd) déclare get_size(), count_files() et list_lines(indent). Écrivez :
FileEntry: sa taille est celle de son propresize, il compte comme1fichier, etlist_lines()affiche l’indentation, puisNAME (SIZE KB).FolderEntry:add(entry)stocke un enfant. Sa taille est la somme des tailles de ses enfants, son nombre de fichiers est la somme des leurs, etlist_lines()affiche l’indentation, puisNAME/ (SIZE KB), puis répertorie chaque enfant avec l’indentation suivie de deux espaces.
Le code fourni construit l’arbre à partir de l’entrée (le nombre de lignes qui suivent, puis folder NAME, file NAME SIZE ou end, qui ferme le dossier courant), le répertorie et affiche les totaux. Pour un dossier Root contenant notes.txt (4 KB) et un dossier Music contenant song.mp3 (300 KB), la sortie est :
Root/ (304 KB)
notes.txt (4 KB)
Music/ (300 KB)
song.mp3 (300 KB)
Total: 304 KB, files: 2Votre code doit être placé dans file_entry.gd et folder_entry.gd. main.gd contient le code fourni pour l’entrée et la sortie et ne peut pas être modifié.
Essayez vous-même
extends Node
func _ready():
# Code d'entrée/sortie fourni : conservez-le tel quel
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()])
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Programmation orientée objet
1Fondamentaux de la POO
Fichiers externesIntroduction à la POOClasses et objetsLe mot-clé selfMéthodesPropriétésConstructeur (_init)Récapitulatif - Calculatrice simple4Polymorphisme
Retour sur la redéfinition des méthodesTypage canardClasses et méthodes abstraitesConception d’interfacesRécapitulatif – Calculateur de formes7Signaux
Qu’est-ce qu’un signal ?Déclarer et émettreSe connecter aux signauxSignaux avec argumentsRécapitulatif - système de santé10Patrons de conception, partie 2
Patron CommandePatron AdaptateurPatron DécorateurPatron Méthode modèlePatron ÉtatPatron Composite2Propriétés de classe
Variables d’instance et variables statiquesFonctions statiquesSetters de propriétésGetters de propriétésPrivé par conventionRécapitulatif - Gestionnaire de comptes bancaires5Encapsulation
Membres publics et privésValidation avec les méthodes setterMasquage de l’informationPropriétés en lecture seuleRécapitulatif - Système de dossiers étudiants8Concepts avancés de la POO
Composition ou héritageClasses internesLes nœuds comme composantsRefCounted ou NodeEnums dans les classes3Héritage
Héritage de baseLe mot-clé superRedéfinition de méthodesChaînes d’héritageVérifier les types avec isRécapitulatif – Hiérarchie des employés6Méthodes spéciales
Introduction aux méthodes virtuellesLa méthode _to_stringItérateurs personnalisésRécapitulatif – Liste personnalisée9Patrons de conception, partie 1
Introduction aux patrons de conceptionPatron SingletonPatron FactoryPatron ObserverPatron StrategyEntraînez-vous par vous-même : Compilateur GDScript en ligne