Patron Composite en Python
Le patron Composite est un patron de conception structurel qui permet de composer des objets en structures arborescentes pour représenter des hiérarchies partie-tout. Le patron Composite permet aux clients de traiter uniformément les objets individuels et les compositions d'objets, sans avoir à se soucier s'ils ont affaire à un objet individuel ou à l'ensemble de la structure composite.
Analogie avec la vie réelle
Imaginez la structure organisationnelle d'un système de fichiers :
- Fichier individuel: est l'unité la plus élémentaire
- Dossier: peut contenir plusieurs fichiers ou d'autres dossiers
- Que vous opériez sur un fichier ou un dossier, vous pouvez effectuer des opérations telles que « ouvrir », « supprimer », « renommer », etc.
Cette relation hiérarchique « partie-tout » est un scénario d'application typique du patron Composite.
Composants principaux du patron Composite
1. Interface de composant (Component)
C'est le cœur du patron Composite, définissant une interface commune pour tous les objets (y compris les nœuds feuilles et les nœuds composites).
Exemple
from typing import List
class FileSystemComponent(ABC):
"""Classe de base abstraite d'un composant de système de fichiers"""
def __init__(self, name: str):
self.name = name
self.parent = None
@abstractmethod
def display(self, indent: int = 0) -> None:
"""Afficher les informations du composant"""
pass
@abstractmethod
def get_size(self) -> int:
"""Obtenir la taille du composant"""
pass
def get_path(self) -> str:
"""Obtenir le chemin complet"""
if self.parent:
return f"{self.parent.get_path()}/{self.name}"
return self.name
2. Nœud feuille (Leaf)
Représente les objets feuilles dans la composition ; les nœuds feuilles n'ont pas de nœuds enfants.
Exemple
"""Classe File - nœud feuille"""
def __init__(self, name: str, size: int):
super().__init__(name)
self._size = size
def display(self, indent: int = 0) -> None:
"""Afficher les informations du fichier"""
spaces = " " * indent
print(f"{spaces}📄 {self.name} ({self._size} bytes)")
def get_size(self) -> int:
"""Renvoie la taille du fichier"""
return self._size
3. Nœud composite (Composite)
Représente des objets composites pouvant contenir des composants enfants, définissant des méthodes pour stocker les composants enfants.
Exemple
"""Classe Directory - nœud composite"""
def __init__(self, name: str):
super().__init__(name)
self._children: List[FileSystemComponent] = []
def add(self, component: FileSystemComponent) -> None:
"""Ajouter un composant enfant"""
component.parent = self
self._children.append(component)
def remove(self, component: FileSystemComponent) -> None:
"""Supprimer le composant enfant"""
self_children.remove(component)
component.parent = None
def display(self, indent: int = 0) -> None:
"""Afficher le répertoire et tous ses composants enfants"""
spaces = " " * indent
{spaces}📄 {self.name}/(f"{spaces}📁 {self.name}/")
# Afficher récursivement tous les composants enfants
for child in self_children:
child.display(indent + 1)
def get_size(self) -> int:
"""Afficher la taille du répertoire (y compris tous les composants enfants)"""
total_size = 0
for child in self_children:
total_size += child.get_size()
return total_size
def find_component(self, name: str) -> FileSystemComponent:
"""Trouver un composant par le nom spécifié"""
for child in self_children:
if child.name == name:
return child
if isinstance(child, Directory):
found = child.find_component(name)
if found:
return found
return None
Exemple complet : simulation de système de fichiers
Démontrons l'application pratique du patron Composite avec un exemple complet :
Exemple
"""Démontrer l'utilisation du patron Composite"""
# Créer le répertoire racine
root = Directory("root")
# Créer un sous-répertoire
documents = Directory("documents")
pictures = Directory("pictures")
music = Directory("music")
# Créer des fichiers
readme = File("README.txt", 1024)
notes = File("notes.md", 2048)
photo1 = File("vacation.jpg", 1536000)
photo2 = File("family.png", 2048000)
song1 = File("song1.mp3", 4096000)
song2 = Fichier("song2.mp3", 5120000)
# Construire la structure de répertoires
root.add(readme)
root.add(documents)
root.add(pictures)
root.add(music)
documents.add(notes)
pictures.add(photo1)
pictures.add(photo2)
music.add(song1)
music.add(song2)
# Afficher toute la structure du système de fichiers
print("=== Structure du système de fichiers ===")
root.display()
print("\n=== Statistiques de taille ===)
print(f"Taille totale du répertoire racine : {root.get_size()} bytes")
print(f"Taille du répertoire Pictures : {pictures.get_size()} bytes")
print(f"Taille du répertoire Music : {music.get_size()} bytes")
print("\n=== Informations sur le chemin ===)
print(f"Chemin du fichier : {photo1.get_path()}")
print(f"Chemin du répertoire : {pictures.get_path()}")
print("\n=== Rechercher un composant ===)
found = root.find_component("song1.mp3")
if found:
print(f"Fichier trouvé : {found.get_path()}")
# Exécuter la démo
if __name__ == "__main__":
demonstrate_composite_pattern()
L'exécution du code ci-dessus produira la sortie suivante :

Diagramme de classes UML du patron Composite

Avantages et inconvénients du patron Composite
Avantages
- Interface unifiée: Les clients peuvent utiliser des objets individuels et des objets composites de manière cohérente
- Principe ouvert/fermé: Il est facile d'ajouter de nouveaux types de composants sans modifier le code existant
- Code client simplifié: Les clients n'ont pas besoin de déterminer s'ils ont affaire à un objet individuel ou à un composite
- Hiérarchie flexible: Permet de construire des structures arborescentes complexes
Inconvénients
- Complexité de conception: Une conception trop généralisée peut rendre le système complexe
- Problèmes de sécurité de type: Dans certains cas, une vérification de type à l'exécution peut être nécessaire
- Considérations de performance: Pour les grandes hiérarchies, les opérations récursives peuvent affecter les performances
Scénarios d'application pratiques
1. Système de composants d'interface graphique
Exemple
"""Classe de base du composant UI"""
def render(self):
pass
def add(self, component):
pass
class Button(UIComponent):
"""Button - nœud feuille"""
def render(self):
print("Rendu du bouton")
class Panel(UIComponent):
"""Panel - nœud composite"""
def __init__(self):
self.children = []
def add(self, component):
self.children.append(component)
def render(self):
print("Début du rendu du panneau")
for child in self.children:
child.render()
print("Fin du panneau de rendu")
2. Gestion de la structure organisationnelle
Exemple
"""Classe de base Employee"""
def get_salary(self):
pass
class Developer(Employee):
"""Developer - nœud feuille"""
def __init__(self, salary):
self.salary = salary
def get_salary(self):
return self.salary
class Department(Employee):
"""Département - nœud composite"""
def __init__(self):
self.employees = []
def add(self, employee):
self.employees.append(employee)
def get_salary(self):
return sum(emp.get_salary() for emp in self.employees)
Exercices pratiques
Exercice 1 : Étendre le système de fichiers
Ajoutez les fonctionnalités suivantes au système de fichiers :
- Implémenter la fonctionnalité de copie de fichiers
- Ajouter une recherche filtrée par type de fichier
- Implémenter un affichage limité à la profondeur du répertoire
Exercice 2 : Conception du système de menus
Concevez un système de menu de restaurant avec les exigences suivantes :
- Un menu peut contenir des plats ou des sous-menus
- Pouvoir calculer le prix total
- Prendre en charge le filtrage des plats par catégorie
Exercice 3 : Organigramme
Créez un système de structure organisationnelle d'entreprise :
- Les employés peuvent être des individus ou des services
- Les services peuvent contenir d'autres employés ou des sous-services
- Pouvoir calculer le nombre total de personnes et le salaire total d'un service
Résumé
Le patron Composite gère les objets individuels et les compositions d'objets via une interface unifiée, ce qui rend le code client plus concis et flexible. Ce patron est particulièrement adapté aux scénarios avec des structures hiérarchiques, comme les systèmes de fichiers, les composants GUI, les structures organisationnelles, etc.
Points clés:
- L'interface de composant définit les opérations communes à tous les objets
- Les nœuds feuilles implémentent les fonctionnalités de base
- Les nœuds composites gèrent les composants enfants et fournissent des opérations d’agrégation
- Les clients n’ont pas besoin de faire la distinction entre les objets individuels et les compositions d’objets