Abstract Factory Pattern

Abstract Factory Pattern

vg

Das Abstract Factory Pattern ist ein Entwurfsmuster, das zur Erzeugung von Objekten aus einer Familie verwandter Klassen dient, ohne die konkreten Klassen zu kennen. Statt direkt Objekte zu erstellen, nutzt der Client eine Schnittstelle, die verschiedene konkrete Fabriken bereitstellt. Jede dieser Fabriken ist für die Erstellung einer bestimmten Produktfamilie zuständig. Dieses Muster eignet sich besonders, wenn das System von der Familie von Objekten abhängig ist und diese Objekte dynamisch zur Laufzeit ausgewählt werden müssen.

Was ist das Abstract Factory Pattern?

Das Abstract Factory Pattern ist ein kreatives Entwurfsmuster, das eine Schnittstelle zur Verfügung stellt, um Objekte zu erzeugen. Dabei wird die konkrete Erstellung der Objekte in konkrete Fabriken ausgelagert. Diese Fabriken sind verantwortlich für die Instanziierung von Produkten einer bestimmten Familie. Der Vorteil liegt darin, dass der Client nur mit der Schnittstelle der Fabrik arbeitet, ohne sich um die Implementierung der konkreten Fabriken kümmern zu müssen.

Ein wichtiger Aspekt des Musters ist die Trennung der Produktfamilien. Eine Familie von Produkten umfasst eine Sammlung von Objekten, die zusammenarbeiten und dieselben abstrakten Schnittstellen implementieren. So kann der Client sicherstellen, dass er nur zusammenpassende Produkte einer Familie verwendet.

Struktur des Abstract Factory Patterns

  1. AbstractFactory: Eine Schnittstelle, die die Methode(n) zur Erstellung von Produkten definiert.
  2. ConcreteFactory: Diese Klassen implementieren die abstrakte Fabrik und erzeugen konkrete Produkte.
  3. AbstractProduct: Eine Schnittstelle, die die gemeinsamen Methoden für eine Produktfamilie definiert.
  4. ConcreteProduct: Konkrete Implementierungen der Produktklassen.
  5. Client: Der Client nutzt die Fabrik, um Produkte zu erzeugen, ohne ihre konkreten Typen zu kennen.

Beispiel des Abstract Factory Patterns in C++

In diesem Beispiel erstellen wir eine Software zur Verwaltung von Möbeln. Es gibt zwei verschiedene Produktfamilien: moderne Möbel und klassische Möbel. Jede Familie enthält einen Stuhl und einen Tisch. Wir implementieren das Abstract Factory Pattern, um sicherzustellen, dass der Client mit verschiedenen Möbeln arbeiten kann, ohne die konkreten Produktklassen zu kennen.

#include <iostream>
#include <string>

// AbstractProduct: Stuhl
class Chair {
public:
    virtual void sit() const = 0;
    virtual ~Chair() = default;
};

// ConcreteProduct: Moderner Stuhl
class ModernChair : public Chair {
public:
    void sit() const override {
        std::cout << "Sitz auf modernem Stuhl." << std::endl;
    }
};

// ConcreteProduct: Klassischer Stuhl
class VictorianChair : public Chair {
public:
    void sit() const override {
        std::cout << "Sitz auf klassischem Stuhl." << std::endl;
    }
};

// AbstractProduct: Tisch
class Table {
public:
    virtual void use() const = 0;
    virtual ~Table() = default;
};

// ConcreteProduct: Moderner Tisch
class ModernTable : public Table {
public:
    void use() const override {
        std::cout << "Verwende modernen Tisch." << std::endl;
    }
};

// ConcreteProduct: Klassischer Tisch
class VictorianTable : public Table {
public:
    void use() const override {
        std::cout << "Verwende klassischen Tisch." << std::endl;
    }
};

// AbstractFactory: Möbel Fabrik
class FurnitureFactory {
public:
    virtual Chair* createChair() const = 0;
    virtual Table* createTable() const = 0;
    virtual ~FurnitureFactory() = default;
};

// ConcreteFactory: Moderne Möbel Fabrik
class ModernFurnitureFactory : public FurnitureFactory {
public:
    Chair* createChair() const override {
        return new ModernChair();
    }

    Table* createTable() const override {
        return new ModernTable();
    }
};

// ConcreteFactory: Klassische Möbel Fabrik
class VictorianFurnitureFactory : public FurnitureFactory {
public:
    Chair* createChair() const override {
        return new VictorianChair();
    }

    Table* createTable() const override {
        return new VictorianTable();
    }
};

// Client-Code
int main() {
    // Wählen der Fabrik
    FurnitureFactory* factory = new ModernFurnitureFactory();

    // Erzeugen der Möbel
    Chair* chair = factory->createChair();
    Table* table = factory->createTable();

    // Nutzung der Möbel
    chair->sit();
    table->use();

    // Aufräumen
    delete chair;
    delete table;
    delete factory;

    return 0;
}

Erklärung des C++-Beispiels

In diesem Beispiel haben wir zwei Produktfamilien: moderne Möbel und klassische Möbel. Jede Familie hat zwei Produkte: einen Stuhl und einen Tisch. Die Struktur des Abstract Factory Patterns ist wie folgt:

  1. AbstractProduct: Die Schnittstellen Chair und Table definieren Methoden, die von allen Produkten implementiert werden müssen. Jedes Produkt muss eine sit()-Methode (für Stühle) und eine use()-Methode (für Tische) bereitstellen.
  2. ConcreteProduct: ModernChair, VictorianChair, ModernTable und VictorianTable sind konkrete Implementierungen der Produktklassen. Sie bieten spezifische Implementierungen der sit()– und use()-Methoden.
  3. AbstractFactory: Die FurnitureFactory-Schnittstelle definiert zwei Methoden: createChair() und createTable(). Diese Methoden müssen von den konkreten Fabriken implementiert werden.
  4. ConcreteFactory: ModernFurnitureFactory und VictorianFurnitureFactory sind die konkreten Fabriken. Jede von ihnen erstellt Stühle und Tische der entsprechenden Produktfamilie. Die createChair()– und createTable()-Methoden erzeugen jeweils ein Objekt der entsprechenden Familie.
  5. Client: Der Client verwendet die Fabrik, um Stühle und Tische zu erstellen, ohne die konkreten Fabriken zu kennen. Im Beispiel wählt der Client die ModernFurnitureFactory, um moderne Möbel zu erzeugen. Der Client nutzt die sit()– und use()-Methoden der Produkte, ohne sich um die Details der konkreten Implementierungen zu kümmern.

Beispiel des Abstract Factory Patterns in Python

Im folgenden Beispiel erstellen wir eine abstrakte Fabrik, die Fahrzeuge produziert. Es gibt zwei Familien von Fahrzeugen: Landfahrzeuge und Luftfahrzeuge. Jede Familie enthält verschiedene Arten von Fahrzeugen.

from abc import ABC, abstractmethod

# Produkt-Interfaces
class Car(ABC):
    @abstractmethod
    def drive(self):
        pass

class Airplane(ABC):
    @abstractmethod
    def fly(self):
        pass

# Konkrete Produkte für Landfahrzeuge
class Sedan(Car):
    def drive(self):
        print("Driving a sedan")

class SUV(Car):
    def drive(self):
        print("Driving an SUV")

# Konkrete Produkte für Luftfahrzeuge
class Jet(Airplane):
    def fly(self):
        print("Flying a jet")

class Helicopter(Airplane):
    def fly(self):
        print("Flying a helicopter")

# Abstract Factory Interface
class VehicleFactory(ABC):
    @abstractmethod
    def create_car(self):
        pass
    
    @abstractmethod
    def create_airplane(self):
        pass

# Konkrete Fabriken
class LandVehicleFactory(VehicleFactory):
    def create_car(self):
        return Sedan()  # Oder return SUV() für ein anderes Auto
    
    def create_airplane(self):
        # Landfahrzeuge stellen keine Flugzeuge her
        raise NotImplementedError("Land vehicle factory does not create airplanes.")

class AirVehicleFactory(VehicleFactory):
    def create_car(self):
        # Luftfahrzeuge stellen keine Autos her
        raise NotImplementedError("Air vehicle factory does not create cars.")
    
    def create_airplane(self):
        return Jet()  # Oder return Helicopter() für ein anderes Flugzeug

# Client-Code
def get_vehicle_info(factory: VehicleFactory):
    try:
        car = factory.create_car()
        car.drive()
    except NotImplementedError:
        print("No car produced by this factory.")

    try:
        airplane = factory.create_airplane()
        airplane.fly()
    except NotImplementedError:
        print("No airplane produced by this factory.")

# Verwendung der Fabriken
land_factory = LandVehicleFactory()
get_vehicle_info(land_factory)

print("\n")

air_factory = AirVehicleFactory()
get_vehicle_info(air_factory)

Erklärung des Beispiels in Python

  1. Produkte (Car und Airplane): Diese abstrakten Klassen definieren die grundlegenden Methoden, die von den konkreten Fahrzeugtypen implementiert werden müssen, wie drive für Autos und fly für Flugzeuge.
  2. Konkrete Produkte (Sedan, SUV, Jet, Helicopter): Diese Klassen implementieren die abstrakten Methoden und spezifizieren das Verhalten der jeweiligen Fahrzeugtypen.
  3. Abstract Factory (VehicleFactory): Eine abstrakte Fabrik, die zwei Methoden definiert: create_car() und create_airplane(). Jede konkrete Fabrik implementiert diese Methoden.
  4. Konkrete Fabriken (LandVehicleFactory, AirVehicleFactory): Diese Fabriken erzeugen jeweils Autos oder Flugzeuge. Die LandVehicleFactory erstellt Autos, während die AirVehicleFactory Flugzeuge erzeugt.
  5. Client-Code (get_vehicle_info): Der Client kann entweder eine LandVehicleFactory oder eine AirVehicleFactory verwenden, um Fahrzeuge zu erhalten, ohne zu wissen, wie diese konkret erzeugt werden.

Ausgabe

kotlinCode kopierenDriving a sedan
No airplane produced by this factory.

Flying a jet
No car produced by this factory.

In diesem Beispiel sehen wir, wie das Abstract Factory Pattern es ermöglicht, unterschiedliche Fahrzeugtypen (Auto und Flugzeug) zu erstellen, ohne die genaue Implementierung der Fahrzeuge in der Client-Klasse zu kennen.

Vorteile des Abstract Factory Patterns

  1. Trennung von Produkten und deren Erstellung: Der Client muss sich nicht mit der Erstellung der konkreten Objekte beschäftigen. Die Fabrik übernimmt diese Verantwortung und stellt die Objekte auf abstrakte Weise bereit.
  2. Flexibilität bei der Auswahl von Produktfamilien: Der Client kann einfach die Fabrik wechseln, um eine andere Produktfamilie zu verwenden. Dies ermöglicht es, zwischen verschiedenen Produktvarianten zu wechseln, ohne den Code des Clients zu ändern.
  3. Erleichterung von Erweiterungen: Wenn eine neue Produktfamilie hinzugefügt werden muss, kann dies durch Erstellen einer neuen Fabrik und neuer Produkte erfolgen, ohne dass der bestehende Code geändert werden muss.
  4. Konsistenz der Produkte: Alle Produkte, die von einer Fabrik erzeugt werden, sind konsistent, da sie zusammenarbeiten und zur gleichen Familie gehören. Dies verhindert Fehler, die durch die Mischung inkompatibler Produkte entstehen könnten.

Nachteile des Abstract Factory Patterns

  1. Erhöhte Komplexität: Das Muster kann die Anzahl der Klassen erhöhen, da für jede Produktfamilie eine separate Fabrik und konkrete Produktklassen erforderlich sind. Dies kann den Code komplexer machen, insbesondere bei großen Systemen.
  2. Schwierigkeiten bei der Erweiterung: Wenn zu viele Produktvarianten existieren, kann das Hinzufügen neuer Produktfamilien schwierig werden, da dies Änderungen in vielen Klassen erfordert.

Fazit

Das Abstract Factory Pattern ist ein äußerst nützliches Entwurfsmuster, wenn es darum geht, konsistente Produktfamilien zu erstellen und den Code flexibel zu halten. Es bietet eine saubere Trennung zwischen der Produkterstellung und der Nutzung der Produkte. In C++ lässt sich das Muster elegant implementieren, indem eine Schnittstelle für die Fabriken und Produkte verwendet wird. Es fördert die Erweiterbarkeit und ermöglicht es dem Client, problemlos zwischen verschiedenen Produktfamilien zu wechseln.

Zurück zur Liste: Liste der Design-Pattern

com

Newsletter Anmeldung

Bleiben Sie informiert! Wir informieren Sie über alle neuen Beiträge (max. 1 Mail pro Woche – versprochen)