Python-Kompositum-Muster

Das Composite Pattern ist ein strukturelles Entwurfsmuster, das es ermöglicht, Objekte zu Baumstrukturen zusammenzusetzen, um Teil-Ganzes-Hierarchien darzustellen. Das Composite Pattern ermöglicht es Clients, einzelne Objekte und Objektkompositionen einheitlich zu behandeln, ohne sich darum kümmern zu müssen, ob sie mit einem einzelnen Objekt oder der gesamten zusammengesetzten Struktur arbeiten.

Analogie aus dem echten Leben

Stellen Sie sich die Organisationsstruktur eines Dateisystems vor:

  • Einzelne Datei: ist die grundlegendste Einheit
  • Ordner: kann mehrere Dateien oder andere Ordner enthalten
  • Egal, ob Sie eine Datei oder einen Ordner bearbeiten, können Sie Vorgänge wie "Öffnen", "Löschen", "Umbenennen" usw. ausführen.

Diese "Teil-Ganzes"-Hierarchiebeziehung ist ein typisches Anwendungsszenario des Composite Patterns.


Kernkomponenten des Composite-Musters

1. Komponentenschnittstelle (Component)

Dies ist der Kern des Composite-Musters und definiert eine gemeinsame Schnittstelle für alle Objekte (einschließlich Blattknoten und Verbundknoten).

Beispiel

from abc import ABC, abstractmethod
from typing import List

class FileSystemComponent(ABC):
    """Abstrakte Basisklasse für Dateisystemkomponenten"""
   
    def __init__(self, name: str):
        self.name = name
        self.parent = None
   
    @abstractmethod
    def display(self, indent: int = 0) -> None:
        """Komponenteninformationen anzeigen"""
        pass
   
    @abstractmethod
    def get_size(self) -> int:
        """Komponentengröße abrufen"""
        pass
   
    def get_path(self) -> str:
        """Vollständigen Pfad abrufen"""
        if self.parent:
            return f"{self.parent.get_path()}/{self.name}"
        return self.name

2. Blattknoten (Leaf)

Stellt Blattobjekte in der Komposition dar; Blattknoten haben keine Kindknoten.

Beispiel

class File(FileSystemComponent):
    """File-Klasse - Blattknoten"""
   
    def __init__(self, name: str, size: int):
        super().__init__(name)
        self._size = size
   
    def display(self, indent: int = 0) -> None:
        """Dateiinformationen anzeigen"""
        spaces = "  " * indent
        print(f"{spaces}📄 {self.name} ({self._size} bytes)")
   
    def get_size(self) -> int:
        """Dateigröße zurückgeben"""
        return self._size

3. Zusammengesetzter Knoten (Composite)

Repräsentiert zusammengesetzte Objekte, die untergeordnete Komponenten enthalten können, und definiert Methoden zum Speichern untergeordneter Komponenten.

Beispiel

class Directory(FileSystemComponent):
    """Directory-Klasse - zusammengesetzter Knoten"""
   
    def __init__(self, name: str):
        super().__init__(name)
        self._children: List[FileSystemComponent] = []
   
    def add(self, component: FileSystemComponent) -> None:
        """Untergeordnete Komponente hinzufügen"""
        component.parent = self
        self._children.append(component)
   
    def remove(self, component: FileSystemComponent) -> None:
        """Untergeordnete Komponente entfernen"""
        self._children.remove(component)
        component.parent = None
   
    def display(self, indent: int = 0) -> None:
        """Verzeichnis und alle seine untergeordneten Komponenten anzeigen"""
        spaces = "  " * indent
        print(f"{spaces}📁 {self.name}/")
       
        # Alle untergeordneten Komponenten rekursiv anzeigen
        for child in self._children:
            child.display(indent + 1)
   
    def get_size(self) -> int:
        """Gesamtgröße des Verzeichnisses berechnen (einschließlich aller untergeordneten Komponenten)"""
        total_size = 0
        for child in self._children:
            total_size += child.get_size()
        return total_size
   
    def find_component(self, name: str) -> FileSystemComponent:
        """Findet eine Komponente mit dem angegebenen Namen"""
        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

Vollständiges Beispiel: Dateisystem-Simulation

Lassen Sie uns die praktische Anwendung des Composite-Musters anhand eines vollständigen Beispiels demonstrieren:

Beispiel

def demonstrate_composite_pattern():
    """Demonstriert die Verwendung des Composite-Musters"""
   
    # Stammverzeichnis erstellen
    root = Directory("root")
   
    # Unterverzeichnis erstellen
    documents = Directory("documents")
    pictures = Directory("pictures")
    music = Directory("music")
   
    # Dateien erstellen
    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 = Datei("song2.mp3", 5120000)
   
    # Verzeichnisstruktur erstellen
    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)
   
    # Die gesamte Dateisystemstruktur anzeigen
    print("=== Dateisystemstruktur ===")
    root.display()
   
    print("\n=== Größenstatistik ===)
    print(f"Gesamtgröße des Root-Verzeichnisses: {root.get_size()} bytes")
    print(f"Größe des Bilder-Verzeichnisses: {pictures.get_size()} bytes")
    print(f"Größe des Musik-Verzeichnisses: {music.get_size()} bytes")
   
    print("\n=== Pfadinformationen ===)
    print(f"Dateipfad: {photo1.get_path()}")
    print(f"Verzeichnispfad: {pictures.get_path()}")
   
    print("\n=== Komponente finden ===)
    found = root.find_component("song1.mp3")
    if found:
        print(f"Gefundene Datei: {found.get_path()}")

# Demo ausführen
if __name__ == "__main__":
    demonstrate_composite_pattern()

Das Ausführen des obigen Codes erzeugt die folgende Ausgabe:


UML-Klassendiagramm des Composite-Musters


Vor- und Nachteile des Composite-Musters

Vorteile

  1. Einheitliche Schnittstelle: Clients können einzelne Objekte und zusammengesetzte Objekte konsistent verwenden
  2. Open/Closed-Prinzip: Es ist einfach, neue Arten von Komponenten hinzuzufügen, ohne vorhandenen Code zu ändern
  3. Vereinfachter Client-Code: Clients müssen nicht feststellen, ob sie es mit einem einzelnen Objekt oder einem zusammengesetzten Objekt zu tun haben
  4. Flexible Hierarchie: Kann komplexe Baumstrukturen aufbauen

Nachteile

  1. Entwurfskomplexität: Eine übermäßig verallgemeinerte Gestaltung kann das System komplex machen
  2. Typensicherheitsprobleme: In einigen Fällen kann eine Typprüfung zur Laufzeit erforderlich sein
  3. Leistungsüberlegungen: Bei großen Hierarchien können rekursive Operationen die Leistung beeinträchtigen

Praktische Anwendungsszenarien

1. GUI-Komponentensystem

Beispiel

class UIComponent:
    """UI-Komponenten-Basisklasse"""
    def render(self):
        pass
    def add(self, component):
        pass

class Button(UIComponent):
    """Button - Blattknoten"""
    def render(self):
        print("Button rendern")

class Panel(UIComponent):
    """Panel - Verbundknoten"""
    def __init__(self):
        self.children = []
   
    def add(self, component):
        self.children.append(component)
   
    def render(self):
        print("Rendern des Panels starten")
        for child in self.children:
            child.render()
        print("Rendering-Panel beenden")

2. Verwaltung der Organisationsstruktur

Beispiel

class Employee:
    """Basisklasse für Mitarbeiter"""
    def get_salary(self):
        pass

class Developer(Employee):
    """Developer - Blattknoten"""
    def __init__(self, salary):
        self.salary = salary
   
    def get_salary(self):
        return self.salary

class Department(Employee):
    """Department - Kompositknoten"""
    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)

Praktische Übungen

Übung 1: Erweiterung des Dateisystems

Fügen Sie die folgenden Funktionen zum Dateisystem hinzu:

  1. Dateikopierfunktion implementieren
  2. Dateitypfilter-Suche hinzufügen
  3. Verzeichnisanzeige mit begrenzter Tiefe implementieren

Übung 2: Entwurf des Menüsystems

Entwerfe ein Restaurant-Menüsystem mit den folgenden Anforderungen:

  • Ein Menü kann Gerichte oder Untermenüs enthalten
  • Gesamtpreis berechnen können
  • Filtern von Gerichten nach Kategorie unterstützen

Übung 3: Organigramm

Erstelle ein System für die Organisationsstruktur eines Unternehmens:

  • Mitarbeiter können Einzelpersonen oder Abteilungen sein
  • Abteilungen können andere Mitarbeiter oder Unterabteilungen enthalten
  • Die Gesamtzahl der Personen und das Gesamtgehalt einer Abteilung berechnen können

Zusammenfassung

Das Composite-Muster behandelt einzelne Objekte und Objektkompositionen über eine einheitliche Schnittstelle, wodurch der Client-Code prägnanter und flexibler wird. Dieses Muster eignet sich besonders für Szenarien mit hierarchischen Strukturen, wie Dateisysteme, GUI-Komponenten, Organisationsstrukturen und so weiter.

Kernpunkte:

  • Die Komponentenschnittstelle definiert gemeinsame Operationen für alle Objekte
  • Blattknoten implementieren grundlegende Funktionalität
  • Kompositknoten verwalten untergeordnete Komponenten und bieten aggregierte Operationen
  • Clients müssen nicht zwischen einzelnen Objekten und Objektkompositionen unterscheiden
Andere Erweiterungen