Patron Composite
Fait partie de la section Object Oriented Programming du Journey Python de Coddy — leçon 54 sur 64.
Le patron Composite traite les objets individuels et les groupes d'objets de manière uniforme. Il crée des structures arborescentes où les éléments individuels et les collections d'éléments partagent la même interface.
Voici des composants simples pour un système de fichiers :
class File:
def __init__(self, name, size):
self.name = name
self.size = size
def get_size(self):
return self.size
def display(self):
return f"File: {self.name} ({self.size}KB)"
class Folder:
def __init__(self, name):
self.name = name
self.children = []
def add(self, item):
self.children.append(item)
def get_size(self):
total = 0
for child in self.children:
total += child.get_size()
return total
def display(self):
result = f"Folder: {self.name}"
for child in self.children:
result += f"\n {child.display()}"
return resultLes fichiers et les dossiers ont les mêmes méthodes (get_size() et display()), ils peuvent donc être traités de manière uniforme.
Construisez une structure de système de fichiers :
# Créer des fichiers
file1 = File("document.txt", 10)
file2 = File("image.jpg", 50)
file3 = File("video.mp4", 200)
# Créer des dossiers
documents = Folder("Documents")
media = Folder("Media")
root = Folder("Root")
# Construire la structure arborescente
documents.add(file1)
media.add(file2)
media.add(file3)
root.add(documents)
root.add(media)Utilisez la structure composite :
print(f"Root size: {root.get_size()}KB")
print(root.display())Créez un autre exemple avec un système de menu :
class MenuItem:
def __init__(self, name, price):
self.name = name
self.price = price
def get_price(self):
return self.price
def show(self):
return f"{self.name}: ${self.price}"
class Menu:
def __init__(self, name):
self.name = name
self.items = []
def add(self, item):
self.items.append(item)
def get_price(self):
total = 0
for item in self.items:
total += item.get_price()
return total
def show(self):
result = f"{self.name} Menu:"
for item in self.items:
result += f"\n {item.show()}"
return result
combo = Menu("Combo")
combo.add(MenuItem("Burger", 8))
combo.add(MenuItem("Fries", 3))
combo.add(MenuItem("Drink", 2))
print(f"Combo price: ${combo.get_price()}")
print(combo.show())Sortie :
Root size: 260KB
Folder: Root
Folder: Documents
File: document.txt (10KB)
Folder: Media
File: image.jpg (50KB)
File: video.mp4 (200KB)
Combo price: $13
Combo Menu:
Burger: $8
Fries: $3
Drink: $2Point clé : Le patron Composite vous permet de traiter les objets individuels et les collections d'objets de la même manière. Les feuilles (éléments individuels) et les composites (groupes) implémentent tous deux la même interface, ce qui facilite le travail avec des structures arborescentes telles que les systèmes de fichiers, les menus ou les organigrammes.
Défi
MoyenDans ce défi, vous allez implémenter une structure de système de fichiers en utilisant le patron de conception Composite. Le patron Composite vous permet de composer des objets dans des structures arborescentes pour représenter des hiérarchies partie-tout, en traitant les objets individuels et les compositions d'objets de manière uniforme.
Le patron Composite se compose de :
- Component : Une classe abstraite qui définit l'interface commune pour toutes les classes concrètes
- Leaf : Représente les objets finaux d'une composition sans sous-éléments
- Composite : Définit le comportement des composants ayant des enfants et stocke les composants enfants
Vous allez implémenter un système de fichiers avec :
- Une classe abstraite
FileSystemComponent(Component) - Une classe
File(Leaf) - Une classe
Directory(Composite) - Une classe
FileSystempour gérer la structure globale
- Implémentez la classe de base abstraite avec les méthodes abstraites appropriées
- Créez des implémentations concrètes pour les fichiers et les répertoires
- Assurez-vous que les répertoires peuvent contenir à la fois des fichiers et d'autres répertoires
- Implémentez des opérations récursives comme le calcul de la taille et l'affichage
- Ajoutez des opérations basées sur le chemin pour ajouter, supprimer et trouver des composants
- Gérez les cas d'erreur de manière appropriée
- Assurez une encapsulation correcte des propriétés des composants
Aide-mémoire
Le Composite Pattern traite les objets individuels et les groupes d'objets de manière uniforme en créant des structures arborescentes où les éléments simples et les collections partagent la même interface.
Exemple de système de fichiers de base :
class File:
def __init__(self, name, size):
self.name = name
self.size = size
def get_size(self):
return self.size
def display(self):
return f"File: {self.name} ({self.size}KB)"
class Folder:
def __init__(self, name):
self.name = name
self.children = []
def add(self, item):
self.children.append(item)
def get_size(self):
total = 0
for child in self.children:
total += child.get_size()
return total
def display(self):
result = f"Folder: {self.name}"
for child in self.children:
result += f"\n {child.display()}"
return resultConstruction de la structure :
# Créer des fichiers
file1 = File("document.txt", 10)
file2 = File("image.jpg", 50)
# Créer des dossiers
documents = Folder("Documents")
root = Folder("Root")
# Construire la structure arborescente
documents.add(file1)
root.add(documents)
# Utiliser de manière uniforme
print(f"Root size: {root.get_size()}KB")
print(root.display())Exemple de système de menu :
class MenuItem:
def __init__(self, name, price):
self.name = name
self.price = price
def get_price(self):
return self.price
def show(self):
return f"{self.name}: ${self.price}"
class Menu:
def __init__(self, name):
self.name = name
self.items = []
def add(self, item):
self.items.append(item)
def get_price(self):
total = 0
for item in self.items:
total += item.get_price()
return total
def show(self):
result = f"{self.name} Menu:"
for item in self.items:
result += f"\n {item.show()}"
return resultComposants du patron :
- Component : Classe abstraite définissant l'interface commune
- Leaf : Objets finaux sans sous-éléments (File, MenuItem)
- Composite : Objets ayant des enfants qui implémentent la même interface (Folder, Menu)
Avantages clés : Traite les objets individuels et les collections de manière uniforme, ce qui facilite le travail avec des structures arborescentes telles que les systèmes de fichiers, les menus ou les organigrammes.
Essayez vous-même
# Importer toutes les classes nécessaires
from file_system import FileSystem
from directory import Directory
from file import File
# Gestionnaire de cas de test complet
test_case = input()
if test_case == "basic_file_test":
file = File("test.txt", 100)
print(f"Name: {file.name}")
print(f"Size: {file.get_size()} KB")
print(file.display())
elif test_case == "basic_directory_test":
documents = Directory("Documents")
file1 = File("resume.pdf", 250)
file2 = File("cover_letter.doc", 180)
documents.add(file1)
documents.add(file2)
print(f"Total size: {documents.get_size()} KB")
print(documents.display())
elif test_case == "file_system_basic_test":
fs = FileSystem()
readme = File("README.md", 50)
fs.add_to_path("/", readme)
print(fs.display())
print(f"Total system size: {fs.get_total_size()} KB")
elif test_case == "nested_directory_test":
fs = FileSystem()
docs = Directory("Documents")
projects = Directory("Projects")
fs.add_to_path("/", docs)
fs.add_to_path("/Documents", projects)
project_file = File("main.py", 300)
readme = File("README.md", 75)
fs.add_to_path("/Documents/Projects", project_file)
fs.add_to_path("/Documents/Projects", readme)
print(fs.display())
elif test_case == "path_operations_test":
fs = FileSystem()
# Créer la structure de répertoires
docs = Directory("Documents")
projects = Directory("Projects")
fs.add_to_path("/", docs)
fs.add_to_path("/Documents", projects)
# Ajouter des fichiers
file1 = File("notes.txt", 120)
file2 = File("project1.py", 450)
fs.add_to_path("/Documents", file1)
fs.add_to_path("/Documents/Projects", file2)
# Tester les opérations sur les chemins
retrieved_docs = fs.get_from_path("/Documents")
retrieved_file = fs.get_from_path("/Documents/Projects/project1.py")
print(f"Documents directory size: {retrieved_docs.get_size()} KB")
print(f"Retrieved file: {retrieved_file.name} ({retrieved_file.get_size()} KB)")
elif test_case == "file_validation_test":
try:
invalid_file = File("negative.txt", -50)
print("Validation failed - should have raised ValueError")
except ValueError as e:
print(f"Caught expected error: {e}")
# Tester les opérations NotImplementedError
valid_file = File("test.txt", 100)
try:
valid_file.add(File("other.txt", 50))
except NotImplementedError as e:
print(f"Add operation error: {e}")
try:
valid_file.get_component("nonexistent")
except NotImplementedError as e:
print(f"Get component error: {e}")
elif test_case == "directory_duplicate_test":
directory = Directory("TestDir")
file1 = File("duplicate.txt", 100)
file2 = File("duplicate.txt", 200)
directory.add(file1)
print("First file added successfully")
try:
directory.add(file2)
print("Duplicate check failed")
except ValueError as e:
print(f"Caught expected duplicate error: {e}")
elif test_case == "component_removal_test":
directory = Directory("TestDir")
file1 = File("file1.txt", 100)
file2 = File("file2.txt", 150)
file3 = File("file3.txt", 200)
directory.add(file1)
directory.add(file2)
directory.add(file3)
print("Initial state:")
print(directory.display())
directory.remove(file2)
print("\nAfter removing file2.txt:")
print(directory.display())
try:
nonexistent = File("ghost.txt", 50)
directory.remove(nonexistent)
except ValueError as e:
print(f"\nRemoval error: {e}")
elif test_case == "recursive_search_test":
root_dir = Directory("root")
subdir1 = Directory("subdir1")
subdir2 = Directory("subdir2")
file1 = File("target.txt", 100)
file2 = File("other.txt", 150)
file3 = File("deep.txt", 200)
root_dir.add(subdir1)
subdir1.add(subdir2)
subdir1.add(file1)
subdir2.add(file3)
root_dir.add(file2)
# Rechercher des fichiers existants
found1 = root_dir.find_component_recursive("target.txt")
found2 = root_dir.find_component_recursive("deep.txt")
not_found = root_dir.find_component_recursive("missing.txt")
print(f"Found target.txt: {found1.name if found1 else 'Not found'}")
print(f"Found deep.txt: {found2.name if found2 else 'Not found'}")
print(f"Found missing.txt: {not_found.name if not_found else 'Not found'}")
elif test_case == "size_calculation_test":
fs = FileSystem()
# Créer une structure complexe
docs = Directory("Documents")
images = Directory("Images")
fs.add_to_path("/", docs)
fs.add_to_path("/", images)
# Ajouter des fichiers avec des tailles connues
doc1 = File("doc1.txt", 100)
doc2 = File("doc2.txt", 200)
img1 = File("img1.jpg", 500)
img2 = File("img2.png", 300)
fs.add_to_path("/Documents", doc1)
fs.add_to_path("/Documents", doc2)
fs.add_to_path("/Images", img1)
fs.add_to_path("/Images", img2)
# Calculer les tailles
docs_size = fs.get_from_path("/Documents").get_size()
images_size = fs.get_from_path("/Images").get_size()
total_size = fs.get_total_size()
print(f"Documents size: {docs_size} KB")
print(f"Images size: {images_size} KB")
print(f"Total size: {total_size} KB")
print(f"Sum verification: {docs_size + images_size == total_size}")
elif test_case == "display_formatting_test":
root = Directory("root")
level1 = Directory("level1")
level2 = Directory("level2")
file1 = File("root_file.txt", 100)
file2 = File("level1_file.txt", 200)
file3 = File("level2_file.txt", 300)
root.add(file1)
root.add(level1)
level1.add(file2)
level1.add(level2)
level2.add(file3)
print("Formatted directory structure:")
print(root.display())
elif test_case == "file_system_path_test":
fs = FileSystem()
try:
# Créer la structure
fs.add_to_path("/", Directory("home"))
fs.add_to_path("/home", Directory("user"))
fs.add_to_path("/home/user", File("profile.txt", 150))
# Tester la récupération
user_dir = fs.get_from_path("/home/user")
profile = fs.get_from_path("/home/user/profile.txt")
print(f"User directory: {user_dir.name}")
print(f"Profile file: {profile.name}")
# Tester la suppression
fs.remove_from_path("/home/user/profile.txt")
print("Profile removed successfully")
# Tenter d'accéder au fichier supprimé
try:
fs.get_from_path("/home/user/profile.txt")
except ValueError as e:
print(f"Expected error accessing removed file: {e}")
except ValueError as e:
print(f"Path operation error: {e}")
elif test_case == "empty_directory_test":
empty_dir = Directory("Empty")
print(f"Empty directory size: {empty_dir.get_size()} KB")
print("Empty directory display:")
print(empty_dir.display())
result = empty_dir.get_component("nonexistent")
print(f"Get nonexistent component: {result}")
elif test_case == "name_property_test":
file = File("original.txt", 100)
directory = Directory("OriginalDir")
print(f"Original file name: {file.name}")
print(f"Original directory name: {directory.name}")
# Tester la définition de noms valides
file.name = "renamed.txt"
directory.name = "RenamedDir"
print(f"Renamed file: {file.name}")
print(f"Renamed directory: {directory.name}")
# Tester la définition d'un nom vide
try:
file.name = ""
print("Empty name validation failed")
except ValueError as e:
print(f"Empty name error: {e}")
elif test_case == "large_file_system_test":
fs = FileSystem()
# Créer plusieurs répertoires et fichiers
directories = ["Documents", "Images", "Videos", "Music"]
file_counts = [5, 3, 2, 4]
base_sizes = [100, 500, 1000, 200]
total_files = 0
total_directories = len(directories)
for i, dir_name in enumerate(directories):
fs.add_to_path("/", Directory(dir_name))
for j in range(file_counts[i]):
file_name = f"file_{j+1}.ext"
file_size = base_sizes[i] + (j * 50)
fs.add_to_path(f"/{dir_name}", File(file_name, file_size))
total_files += 1
print(f"Created {total_directories} directories")
print(f"Created {total_files} files")
print(f"Total system size: {fs.get_total_size()} KB")
print("\nSystem structure:")
print(fs.display())
elif test_case == "edge_cases_test":
# Tester un fichier de taille 0
zero_file = File("empty.txt", 0)
print(f"Zero size file: {zero_file.get_size()} KB")
# Tester un répertoire avec un nom très long
long_name = "A" * 100
long_dir = Directory(long_name)
print(f"Long directory name length: {len(long_dir.name)}")
# Tester une structure profondément imbriquée
current = Directory("level0")
root = current
for i in range(1, 6):
next_level = Directory(f"level{i}")
current.add(next_level)
current = next_level
# Ajouter un fichier au niveau le plus profond
deep_file = File("deep.txt", 100)
current.add(deep_file)
print("Deep nesting test:")
print(root.display())
# Tester les opérations sur des structures vides
empty = Directory("Empty")
try:
empty.remove(File("ghost.txt", 50))
except ValueError as e:
print(f"Empty structure removal error: {e}")Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Object Oriented Programming
1Les fondamentaux de la POO
Fichiers externesIntroduction à la POOClasses vs ObjetsLe paramètre selfMéthodesAttributsMéthode constructeur (__init__)Récapitulatif - Calculatrice simple4Héritage
Héritage de baseLa fonction super()Redéfinition de méthodeHéritage multipleOrdre de résolution des méthodesRécapitulatif - Hiérarchie des employés7Méthodes spéciales
Introduction aux méthodes magiquesSurcharge d'opérateursMéthodes magiques de conteneurRécapitulatif - Liste personnalisée10Patrons de conception, Partie 1
Introduction aux patrons de conceptionPatron SingletonPatron FactoryPatron ObserverPatron Strategy2Décorateurs
Introduction aux décorateursDécorateur de propriétéDécorateur de méthode statiqueDécorateur de méthode de classe5Polymorphisme
Retour sur la redéfinition de méthodeDuck TypingClasses et méthodes abstraitesConception d'interfacesRécapitulatif - Calculateur de formes8Concepts avancés de la POO
Composition vs HéritageMixinsMéthodes statiques et de classeDécorateurs de classeGestionnaires de contexte11Patrons de conception, Partie 2
Patron CommandePatron AdaptateurPatron DécorateurPatron Méthode TemplatePatron ÉtatPatron Composite3Propriétés de classe
Variables d'instance vs variables de classeDécorateurs de propriétéAttributs privésRécapitulatif - Gestionnaire de compte bancaire