Menu
Coddy logo textTech

Kompositum-Muster

Teil des Abschnitts Object Oriented Programming der Python-Journey von Coddy — Lektion 54 von 64.

Das Composite Pattern behandelt Einzelobjekte und Gruppen von Objekten einheitlich. Es erzeugt Baumstrukturen, in denen sowohl einzelne Elemente als auch Sammlungen von Elementen dieselbe Schnittstelle gemeinsam nutzen.

Hier sind einfache Komponenten für ein Dateisystem:

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 result

Sowohl Dateien als auch Ordner verfügen über dieselben Methoden (get_size() und display()), sodass sie einheitlich behandelt werden können.

Erstellen Sie eine Dateisystemstruktur:

# Dateien erstellen
file1 = File("document.txt", 10)
file2 = File("image.jpg", 50)
file3 = File("video.mp4", 200)

# Ordner erstellen
documents = Folder("Documents")
media = Folder("Media")
root = Folder("Root")

# Die Baumstruktur aufbauen
documents.add(file1)
media.add(file2)
media.add(file3)
root.add(documents)
root.add(media)

Verwenden Sie die Composite-Struktur:

print(f"Root size: {root.get_size()}KB")
print(root.display())

Erstellen Sie ein weiteres Beispiel mit einem Menüsystem:

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())

Ausgabe:

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: $2

Kernpunkt: Das Composite Pattern ermöglicht es Ihnen, einzelne Objekte und Sammlungen von Objekten auf die gleiche Weise zu behandeln. Sowohl Blätter (einzelne Elemente) als auch Komposita (Gruppen) implementieren dieselbe Schnittstelle, was die Arbeit mit Baumstrukturen wie Dateisystemen, Menüs oder Organigrammen erleichtert.

challenge icon

Aufgabe

Mittel

In dieser Herausforderung implementieren Sie eine Dateisystemstruktur unter Verwendung des Composite-Entwurfsmusters. Das Composite-Muster ermöglicht es Ihnen, Objekte zu Baumstrukturen zusammenzufügen, um Teil-Ganzes-Hierarchien darzustellen, wobei einzelne Objekte und Kompositionen von Objekten einheitlich behandelt werden.

Das Composite-Muster besteht aus:

  • Component: Eine abstrakte Klasse, die die gemeinsame Schnittstelle für alle konkreten Klassen definiert
  • Leaf: Repräsentiert Endobjekte einer Komposition ohne Unterelemente
  • Composite: Definiert das Verhalten für Komponenten, die Kinder haben, und speichert Kindkomponenten

Sie werden ein Dateisystem implementieren mit:

  • Einer abstrakten FileSystemComponent-Klasse (Component)
  • Einer File-Klasse (Leaf)
  • Einer Directory-Klasse (Composite)
  • Einer FileSystem-Klasse zur Verwaltung der Gesamtstruktur
  1. Implementieren Sie die abstrakte Basisklasse mit entsprechenden abstrakten Methoden
  2. Erstellen Sie konkrete Implementierungen für Dateien und Verzeichnisse
  3. Stellen Sie sicher, dass Verzeichnisse sowohl Dateien als auch andere Verzeichnisse enthalten können
  4. Implementieren Sie rekursive Operationen wie Größenberechnung und Anzeige
  5. Fügen Sie pfadbasierte Operationen zum Hinzufügen, Entfernen und Finden von Komponenten hinzu
  6. Behandeln Sie Fehlerfälle angemessen
  7. Gewährleisten Sie eine ordnungsgemäße Kapselung der Komponenteneigenschaften

Spickzettel

Das Composite Pattern (Kompositum-Muster) behandelt Einzelobjekte und Gruppen von Objekten einheitlich, indem es Baumstrukturen erstellt, in denen sowohl einzelne Elemente als auch Sammlungen dieselbe Schnittstelle teilen.

Einfaches Dateisystem-Beispiel:

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 result

Aufbau der Struktur:

# Dateien erstellen
file1 = File("document.txt", 10)
file2 = File("image.jpg", 50)

# Ordner erstellen
documents = Folder("Documents")
root = Folder("Root")

# Baumstruktur aufbauen
documents.add(file1)
root.add(documents)

# Einheitlich verwenden
print(f"Root size: {root.get_size()}KB")
print(root.display())

Menüsystem-Beispiel:

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

Muster-Komponenten:

  • Component: Abstrakte Klasse, die die gemeinsame Schnittstelle definiert
  • Leaf: Endobjekte ohne Unterelemente (File, MenuItem)
  • Composite: Objekte mit Kindelementen, die dieselbe Schnittstelle implementieren (Folder, Menu)

Hauptvorteile: Behandelt Einzelobjekte und Sammlungen einheitlich, was die Arbeit mit Baumstrukturen wie Dateisystemen, Menüs oder Organigrammen erleichtert.

Probier es selbst

# Importiere alle notwendigen Klassen
from file_system import FileSystem
from directory import Directory
from file import File

# Umfassender Testfall-Handler
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()
    
    # Verzeichnisstruktur erstellen
    docs = Directory("Documents")
    projects = Directory("Projects")
    fs.add_to_path("/", docs)
    fs.add_to_path("/Documents", projects)
    
    # Dateien hinzufügen
    file1 = File("notes.txt", 120)
    file2 = File("project1.py", 450)
    fs.add_to_path("/Documents", file1)
    fs.add_to_path("/Documents/Projects", file2)
    
    # Pfadoperationen testen
    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}")
    
    # Teste NotImplementedError Operationen
    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)
    
    # Suche nach existierenden Dateien
    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()
    
    # Komplexe Struktur erstellen
    docs = Directory("Documents")
    images = Directory("Images")
    
    fs.add_to_path("/", docs)
    fs.add_to_path("/", images)
    
    # Dateien mit bekannten Größen hinzufügen
    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)
    
    # Größen berechnen
    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:
        # Struktur erstellen
        fs.add_to_path("/", Directory("home"))
        fs.add_to_path("/home", Directory("user"))
        fs.add_to_path("/home/user", File("profile.txt", 150))
        
        # Abruf testen
        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}")
        
        # Entfernung testen
        fs.remove_from_path("/home/user/profile.txt")
        print("Profile removed successfully")
        
        # Versuche auf entfernte Datei zuzugreifen
        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}")
    
    # Teste das Setzen gültiger Namen
    file.name = "renamed.txt"
    directory.name = "RenamedDir"
    
    print(f"Renamed file: {file.name}")
    print(f"Renamed directory: {directory.name}")
    
    # Teste das Setzen eines leeren Namens
    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()
    
    # Erstelle mehrere Verzeichnisse und Dateien
    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":
    # Teste Datei mit Größe 0
    zero_file = File("empty.txt", 0)
    print(f"Zero size file: {zero_file.get_size()} KB")
    
    # Teste Verzeichnis mit sehr langem Namen
    long_name = "A" * 100
    long_dir = Directory(long_name)
    print(f"Long directory name length: {len(long_dir.name)}")
    
    # Teste tief verschachtelte Struktur
    current = Directory("level0")
    root = current
    
    for i in range(1, 6):
        next_level = Directory(f"level{i}")
        current.add(next_level)
        current = next_level
    
    # Datei auf tiefster Ebene hinzufügen
    deep_file = File("deep.txt", 100)
    current.add(deep_file)
    
    print("Deep nesting test:")
    print(root.display())
    
    # Teste Operationen auf leeren Strukturen
    empty = Directory("Empty")
    try:
        empty.remove(File("ghost.txt", 50))
    except ValueError as e:
        print(f"Empty structure removal error: {e}")
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Object Oriented Programming