Python-Builder-Muster

Das Builder-Muster ermöglicht es uns, mit demselben Konstruktionsprozess unterschiedliche Objektdarstellungen zu erzeugen. Stellen Sie sich den Bau eines Hauses vor: Ob Sie eine Villa oder eine Wohnung bauen, müssen Sie Schritte wie das Legen des Fundaments, das Errichten von Wänden und die Installation von Sanitär- und Elektroanlagen durchlaufen, aber die Endergebnisse sind völlig unterschiedlich.

Kernidee

Das Builder-Muster trennt den Konstruktionsprozess eines komplexen Objekts von seiner Darstellung und ermöglicht es demselben Konstruktionsprozess, unterschiedliche Darstellungen zu erzeugen. Dieses Muster eignet sich besonders für die Erstellung komplexer Objekte, die aus mehreren Komponenten bestehen.


Warum brauchen wir das Builder-Muster?

Probleme mit herkömmlichen Konstruktionsmethoden

Betrachten wir zunächst ein Beispiel, das das Builder-Muster nicht verwendet:

Beispiel

class Computer:
    def __init__(self, cpu, memory, storage, graphics_card, monitor):
        self.cpu = cpu
        self.memory = memory
        self.storage = storage
        self.graphics_card = graphics_card
        self.monitor = monitor
   
    def __str__(self):
        return f"Computer: CPU={self.cpu}, Arbeitsspeicher={self.memory}GB, Speicher={self.storage}GB, Grafik={self.graphics_card}, Monitor={self.monitor}"

# Computerobjekt erstellen
computer = Computer("Intel i7", 16, 512, "NVIDIA RTX 3060", "27-inch 4K")
print(computer)

Nachteile dieses Ansatzes:

  • Zu viele Konstruktorparameter, was die Wartung erschwert
  • Die Reihenfolge aller Parameter muss man sich merken
  • Der Erstellungsprozess ist nicht flexibel genug
  • Schlechte Lesbarkeit des Codes

Bestandteile des Builder-Musters

Das Builder-Muster umfasst vier Hauptrollen:

1. Produkt

Das komplexe Objekt, das erstellt werden soll

2. Builder (Abstrakter Builder)

Definiert die abstrakte Schnittstelle zum Erstellen der verschiedenen Teile eines Produkts

3. ConcreteBuilder (Konkreter Builder)

Implementiert die Builder-Schnittstelle und konstruiert und montiert die verschiedenen Teile

4. Direktor

Erstellt ein Objekt, das die Builder-Schnittstelle verwendet


Vollständige Code-Implementierung

Lassen Sie uns das Builder-Muster anhand eines Beispiels für eine Computerkonfiguration vollständig implementieren:

1. Produktklasse

Beispiel

class Computer:
    """Computer-Produktklasse"""
    def __init__(self):
        self.cpu = None
        self.memory = None
        self.storage = None
        self.graphics_card = None
        self.monitor = None
   
    def __str__(self):
        specs = []
        if self.cpu:
            specs.append(f"CPU: {self.cpu}")
        if self.memory:
            specs.append(f"Arbeitsspeicher: {self.memory}GB")
        if self.storage:
            specs.append(f"Speicher: {self.storage}GB")
        if self.graphics_card:
            specs.append(f"Grafikkarte: {self.graphics_card}")
        if self.monitor:
            specs.append(f"Monitor: {self.monitor}")
       
        return "Computerkonfiguration:\n" + "\n".join(f" - {spec}" for spec in specs)

2. Abstrakter Builder (Builder)

Beispiel

from abc import ABC, abstractmethod

class ComputerBuilder(ABC):
    """Abstrakte Klasse Computer Builder"""
   
    def __init__(self):
        self.computer = Computer()
   
    @abstractmethod
    def build_cpu(self):
        pass
   
    @abstractmethod
    def build_memory(self):
        pass
   
    @abstractmethod
    def build_storage(self):
        pass
   
    @abstractmethod
    def build_graphics_card(self):
        pass
   
    @abstractmethod
    def build_monitor(self):
        pass
   
    def get_computer(self):
        return self.computer

3. ConcreteBuilder (Konkreter Builder)

Beispiel

class GamingComputerBuilder(ComputerBuilder):
    """Gaming-Computer-Builder"""
   
    def build_cpu(self):
        self.computer.cpu = "Intel i9-13900K"
   
    def build_memory(self):
        self.computer.memory = 32
   
    def build_storage(self):
        self.computer.storage = 2000  # 2TB
   
    def build_graphics_card(self):
        self.computer.graphics_card = "NVIDIA RTX 4090"
   
    def build_monitor(self):
        self.computer.monitor = "32-inch 4K 144Hz"

class OfficeComputerBuilder(ComputerBuilder):
    """Bürocomputer-Ersteller"""
   
    def build_cpu(self):
        self.computer.cpu = "Intel i5-13400"
   
    def build_memory(self):
        self.computer.memory = 16
   
    def build_storage(self):
        self.computer.storage = 512
   
    def build_graphics_card(self):
        self.computer.graphics_card = "Integrierte Grafik"
   
    def build_monitor(self):
        self.computer.monitor = "24-inch 1080P"

4. Direktor

Beispiel

class ComputerDirector:
    """Computer-Builder-Direktor"""
   
    def __init__(self, builder):
        self.builder = builder
   
    def construct_computer(self):
        """Der vollständige Prozess zum Bau eines Computers"""
        self.builder.build_cpu()
        self.builder.build_memory()
        self.builder.build_storage()
        self.builder.build_graphics_card()
        self.builder.build_monitor()
   
    def get_computer(self):
        return self.builder.get_computer()

Anwendungsbeispiel

Nun wollen wir sehen, wie man das Builder-Muster verwendet, um verschiedene Arten von Computern zu erstellen:

Beispiel

def main():
    print("=== Builder-Muster-Demo ===\n\n")
   
    # Gaming-Computer erstellen
    print("1. Gaming-Computer wird erstellt:")
    gaming_builder = GamingComputerBuilder()
    director = ComputerDirector(gaming_builder)
    director.construct_computer()
    gaming_computer = director.get_computer()
    print(gaming_computer)
   
    print("\n" + "="*50 + "\n")
   
    # Bürocomputer erstellen
    print("2. Bürocomputer wird erstellt:")
    office_builder = OfficeComputerBuilder()
    director = ComputerDirector(office_builder)
    director.construct_computer()
    office_computer = director.get_computer()
    print(office_computer)
   
    print("\n" + "="*50 + "\n")
   
    # Benutzerdefinierten Computer erstellen
    print("3. Zusammenbau eines benutzerdefinierten Computers:")
    custom_builder = CustomComputerBuilder()
    custom_builder.build_cpu("AMD Ryzen 7 7800X3D")
    custom_builder.build_memory(64)
    custom_builder.build_storage(4000)
    custom_builder.build_graphics_card("AMD RX 7900 XTX")
    custom_builder.build_monitor("34-inch gebogener Ultrawide-Monitor")
    custom_computer = custom_builder.get_computer()
    print(custom_computer)

# Benutzerdefinierter Builder (Erstellen mit optionalen Schritten)
class CustomComputerBuilder:
    """Benutzerdefinierter Computer-Builder"""
   
    def __init__(self):
        self.computer = Computer()
   
    def build_cpu(self, cpu):
        self.computer.cpu = cpu
        return self  # self zurückgeben, um verkettete Aufrufe zu unterstützen
   
    def build_memory(self, memory):
        self.computer.memory = memory
        return self
   
    def build_storage(self, storage):
        self.computer.storage = storage
        return self
   
    def build_graphics_card(self, graphics_card):
        self.computer.graphics_card = graphics_card
        return self
   
    def build_monitor(self, monitor):
        self.computer.monitor = monitor
        return self
   
    def get_computer(self):
        return self.computer

if __name__ == "__main__":
    main()

Varianten des Builder-Musters

1. Fluent-Interface-Builder

Beispiel

class Pizza:
    def __init__(self):
        self.size = None
        self.cheese = False
        self.pepperoni = False
        self.mushrooms = False
   
    def __str__(self):
        ingredients = []
        if self.cheese: ingredients.append("Cheese")
        if self.pepperoni: ingredients.append("Italian sausage")
        if self.mushrooms: ingredients.append("Pilze")
        return f"{self.size}-inch Pizza, Beläge: {', '.join(ingredients)}"

class PizzaBuilder:
    def __init__(self, size):
        self.pizza = Pizza()
        self.pizza.size = size
   
    def add_cheese(self):
        self.pizza.cheese = True
        return self
   
    def add_pepperoni(self):
        self.pizza.pepperoni = True
        return self
   
    def add_mushrooms(self):
        self.pizza.mushrooms = True
        return self
   
    def build(self):
        return self.pizza

# Verwende die Fluent-Schnittstelle
pizza = (PizzaBuilder(12)
         .add_cheese()
         .add_pepperoni()
         .add_mushrooms()
         .build())
print(pizza)

Vor- und Nachteile des Builder-Musters

Vorteile

Vorteile Beschreibung
Gute Kapselung Der Konstruktionsprozess ist von der Repräsentation getrennt; Clients müssen keine internen Details kennen.
Starke Erweiterbarkeit Neue konkrete Builder lassen sich bequem hinzufügen.
Bessere Kontrolle Ermöglicht eine feinere Kontrolle über den Konstruktionsprozess.
Lesbarkeit des Codes Code ist klarer, wenn verkettete Aufrufe verwendet werden.

Nachteile

Nachteile Beschreibung
Erhöht die Komplexität Erfordert die Definition mehrerer Klassen, was die Systemkomplexität erhöht.
Nicht geeignet, wenn sich Produkte stark unterscheiden Wenn sich die Produkte stark voneinander unterscheiden, ist das Builder-Muster nicht sehr geeignet.
Builder hängen vom Produkt ab Der Builder muss die spezifischen Details des Produkts kennen.

Geeignete Szenarien

Fälle, die für das Builder-Muster geeignet sind:

  1. Erstellen komplexer Objekte: Es müssen komplexe Objekte erstellt werden, die aus mehreren Teilen bestehen.
  2. Stabiler Konstruktionsprozess: Der Konstruktionsprozess ist relativ stabil, aber die Komponenten des Objekts ändern sich häufig.
  3. Mehrere Darstellungen: Das zu erstellende Objekt hat mehrere unterschiedliche Darstellungen.
  4. Überlappende Konstruktoren vermeiden: Vermeiden Sie die Verwendung von Konstruktoren mit vielen Parametern.

Praktische Anwendungsbeispiele:

  • Dokumentenkonverter: Konvertieren Sie Dokumente in verschiedene Formate wie PDF, Word, HTML.
  • Mahlzeitenzubereitung: Bereiten Sie Mahlzeiten für verschiedene Sets zu (Kombinationen aus Burgern, Pommes, Getränken).
  • UI-Komponentenkonstruktion: Erstellen Sie komplexe Benutzeroberflächenkomponenten.
  • Berichtsgenerierung: Generieren Sie Berichte, die verschiedene Teile enthalten (Kopfzeile, Daten, Diagramme).

Übungsaufgaben

Übung 1: Implementieren Sie einen Auto-Builder

Versuchen Sie, ein Auto-Builder-Muster mit den folgenden Komponenten zu implementieren:

  • Motor (Benzin, Elektro, Hybrid)
  • Karosseriefarbe (rot, blau, schwarz, weiß)
  • Radgröße (16 Zoll, 18 Zoll, 20 Zoll)
  • Innenausstattung (Leder, Stoff)

Übung 2: Den Computer-Builder verbessern

Füge dem Computer-Builder die folgenden Funktionen hinzu:

  • Optionale Komponenten (z. B., ob ein optisches Laufwerk eingebaut werden soll)
  • Validierungslogik (z. B., der Arbeitsspeicher darf nicht weniger als 4GB betragen)
  • Funktion zur Preisberechnung

Zusammenfassung

Das Builder-Muster ist ein leistungsstarkes Erzeugungs-Entwurfsmuster. Indem es den Konstruktionsprozess eines komplexen Objekts in mehrere einfache Schritte zerlegt, macht es den Erstellungsprozess flexibler und kontrollierbarer. Obwohl es die Systemkomplexität erhöht, kann das Builder-Muster in Szenarien, in denen komplexe Objekte erstellt werden müssen und der Konstruktionsprozess relativ fest ist, die Wartbarkeit und Skalierbarkeit des Codes erheblich verbessern.

Denke an die Kernidee von Entwurfsmustern:Verwende Muster nicht um der Muster willen, sondern um konkrete Designprobleme zu lösen.Entscheide in der tatsächlichen Entwicklung anhand der konkreten Anforderungen, ob du das Builder-Muster verwendest.

Andere Erweiterungen