Fabrikmethode

Einführung

Stell dir vor, du arbeitest in einem Projekt mit vielen unterschiedlichen Objekten – zum Beispiel Fahrzeugen, Benachrichtigungen oder Spielfiguren. Jedes Mal, wenn ein neuer Typ hinzukommt, musst du den bestehenden Code anpassen.

Das kostet Zeit, macht den Code unübersichtlich und erhöht das Fehlerrisiko.

Um diese Probleme zu vermeiden, brauchst du ein Muster, das die Objekterstellung vom restlichen Code entkoppelt und flexibel gestaltet. Genau das bietet dir das Factory Method Pattern. In dieser Einheit erfährst du, wie es funktioniert und warum es deine Softwareentwicklung deutlich erleichtert.

Überleitung

Nach dieser Lerneinheit kannst du:

  1. Das Factory Method Pattern definieren und den Zweck erklären. Du kannst beschreiben, wie das Pattern funktioniert und warum es zur Kategorie der Erzeugungsmuster gehört.

  2. Die Struktur des Patterns nachvollziehen. Du erkennst die Rollen von Produkt-Interface, konkreten Produkten, Factory-Klasse und Client.

  3. Vor- und Nachteile benennen und bewerten. Du kannst erklären, wann der Einsatz sinnvoll ist und wann es sich um unnötige Komplexität handelt.

  4. Das Pattern auf praktische Szenarien anwenden. Du bist in der Lage, anhand von Beispielen wie Fahrzeugerstellung oder Notification-System eigene Factory-Implementierungen zu entwickeln.

Überleitung

Das Factory Method Pattern (auf Deutsch: Fabrikmethode) ist ein Entwurfsmuster aus der Softwareentwicklung. Es gehört zur Kategorie der Erzeugungsmuster und hat das Ziel, die Objekterstellung flexibel und erweiterbar zu gestalten.

Schauen wir uns das Problem ohne Factory Method an

Problemstellung ohne Factory Method

Wenn du eine Anwendung entwickelst und unterschiedliche Objekte erzeugen musst, zum Beispiel Charaktere in einem Spiel, sieht der naive Ansatz oft so aus:

public class SpielClient {
    public void starteSpiel() {
        Charakter held;
        if (spielerWahl.equals("Krieger")) {
            held = new Krieger("Stark", 100, 80);
        } else if (spielerWahl.equals("Magier")) {
            held = new Magier("Weise", 60, 120);
        } else if (spielerWahl.equals("Bogenschütze")) {
            held = new Bogenschuetze("Schnell", 70, 90);
        }
        // Problem: Was passiert bei neuen Charaktertypen?
    }
}

Erklärung des Codes

Dieser Code zeigt ein Problem bei der Objekterzeugung (dem “Bauen” von Objekten), wenn diese von einer Benutzereingabe (spielerWahl) abhängt.

  1. SpielClient: Dies ist eine Klasse, die das Spiel steuert.
  2. starteSpiel(): Diese Methode startet das Spiel und muss den Charakter (Held) des Spielers erstellen.
  3. if/elif-Kette: Die Methode prüft die Variable spielerWahl (die hier nicht definiert ist, aber von außen kommen würde).
    • Je nachdem, was der Spieler gewählt hat (“Krieger”, “Magier” etc.), wird explizit ein neues Objekt der jeweiligen Klasse (new Krieger(...), new Magier(...)) erstellt und in der held-Variable gespeichert.
  4. Das Problem (im Kommentar): Die starteSpiel-Methode ist stark an die konkreten Charakterklassen gekoppelt. Wenn ein neuer Charaktertyp (z.B. “Schurke”) hinzugefügt wird, muss diese if/elif-Kette im SpielClient geändert werden. Das macht den Code schwer wartbar und verstößt gegen das Open/Closed-Prinzip (offen für Erweiterung, geschlossen für Änderung).

Die Lösung: Factory Method

Das Factory Method Pattern löst diese Probleme, indem es die Objekterstellung in eine separate Methode auslagert. Der Client muss nicht mehr wissen, wie ein Objekt genau erzeugt wird – er ruft nur die passende Fabrikmethode auf.

Grundidee:

  • Statt Objekte direkt mit new zu erstellen, rufst du eine Factory-Methode auf.
  • Die Factory entscheidet, welches konkrete Objekt erzeugt wird.

Die Einfache Fabrik (Simple Factory)

Bevor wir das klassische Fabrikmethoden-Muster betrachten, beginnen wir mit einer sehr verbreiteten und einfacheren Variante: der Einfachen Fabrik (oft auch als Static Factory bezeichnet).

Ihr Hauptmerkmal ist eine zentrale Klasse mit einer einzigen Methode, die für die Erzeugung verschiedener Objekte zuständig ist. Diese Methode erhält typischerweise einen Parameter (z.B. einen String oder eine Enum), der bestimmt, welche konkrete Klasse instanziiert werden soll. Obwohl sie nicht streng dem klassischen “Factory Method”-Pattern entspricht, löst sie das Kernproblem: Die Entkopplung des Clients von der direkten Erstellung konkreter Objekte.

Das folgende Beispiel mit den Fahrzeugen demonstriert dieses Prinzip perfekt.

Praktisches Beispiel: Fahrzeug-Factory

Wir betrachten ein Beispiel aus der Praxis, bei dem verschiedene Fahrzeugtypen erzeugt werden.

1. Das Produkt-Interface

public interface Fahrzeug {
    void starten();   // Startet das Fahrzeug
    void fahren();    // Fahrzeug führt eine Fahrt aus
    String getTyp();  // Gibt den Typ des Fahrzeugs zurück
}

Erklärung des Codes

Dieser Java-Code definiert ein interface (eine Schnittstelle) namens Fahrzeug.

  1. Was ist ein Interface? Ein interface ist ein reiner “Bauplan” oder “Vertrag”. Es legt fest, welche Methoden (Funktionen) eine Klasse haben muss, wenn sie diesen Vertrag “unterschreibt” (implementiert). Es sagt aber nicht, wie diese Methoden funktionieren.
  2. Fahrzeug: Dies ist der Name des Vertrags.
  3. Die Methoden (starten, fahren, getTyp):
    • Dies sind abstrakte Methoden. Sie haben keinen Code-Körper (keine {...}).
    • void starten(): Jede Klasse, die Fahrzeug ist, muss eine starten-Funktion haben.
    • void fahren(): Sie muss auch eine fahren-Funktion haben.
    • String getTyp(): Sie muss eine getTyp-Funktion haben, die einen Text (String) zurückgibt.

Zweck: Eine Klasse wie Auto oder Motorrad kann nun implements Fahrzeug schreiben. Java zwingt diese Klasse dann dazu, alle drei Methoden (starten, fahren, getTyp) konkret auszuprogrammieren.

Praktisches Beispiel: Fahrzeug-Factory

2. Konkrete Produkte

public class Auto implements Fahrzeug {
    @Override
    public void starten() {
        System.out.println("Auto: Motor startet!");
    }
 
    @Override
    public void fahren() {
        System.out.println("Auto fährt auf der Straße");
    }
 
    @Override
    public String getTyp() {
        return "Auto";
    }
}
 
public class Motorrad implements Fahrzeug {
    @Override
    public void starten() {
        System.out.println("Motorrad: Motor brummt!");
    }
 
    @Override
    public void fahren() {
        System.out.println("Motorrad fährt schnell");
    }
 
    @Override
    public String getTyp() {
        return "Motorrad";
    }
}

Erklärung des Codes

Dieser Code zeigt zwei konkrete Klassen (Auto und Motorrad), die den “Vertrag” (das interface) Fahrzeug aus dem vorigen Beispiel implementieren.

  1. implements Fahrzeug: Diese Anweisung bedeutet, dass die Klasse (z.B. Auto) “unterschreibt” und verspricht, alle im Fahrzeug-Interface definierten Methoden bereitzustellen.
  2. @Override: Diese Markierung (Annotation) zeigt an, dass die folgende Methode (starten, fahren, getTyp) eine Methode aus dem Interface Fahrzeug überschreibt bzw. konkret implementiert.
  3. Spezifisches Verhalten: Obwohl beide Klassen dieselben Methoden (starten, fahren) haben müssen, ist ihre Implementierung (der Code-Inhalt) unterschiedlich. Das Auto startet anders als das Motorrad. Das ist der Kernpunkt von Interfaces: Sie definieren ein einheitliches “Was” (eine starten-Methode), aber jede Klasse entscheidet selbst über das “Wie”.

Praktisches Beispiel: Fahrzeug-Factory

3. Die Factory-Klasse

public class FahrzeugFactory {
    // Factory-Methode zur Erstellung von Fahrzeugen
    public static Fahrzeug erstelleFahrzeug(String typ) {
        switch (typ.toLowerCase()) {
            case "auto":
                return new Auto();      // Erstellt ein Auto
            case "motorrad":
                return new Motorrad(); // Erstellt ein Motorrad
            default:
                throw new IllegalArgumentException("Unbekannter Fahrzeugtyp: " + typ);
        }
    }
}

Erklärung des Codes

Dieser Code implementiert das (Simple) Factory-Muster. Es löst das Problem aus dem SpielClient-Beispiel (die if/elif-Kette zur Objekterzeugung), indem es die Erstellung von Objekten zentralisiert.

  1. FahrzeugFactory: Dies ist eine Klasse, die als “Fabrik” dient. Ihre einzige Aufgabe ist es, Fahrzeug-Objekte zu erstellen.
  2. public static Fahrzeug erstelleFahrzeug(String typ): Dies ist die eigentliche Fabrikmethode.
    • static: Die Methode gehört zur Klasse selbst, nicht zu einem Objekt. Man kann sie direkt aufrufen (FahrzeugFactory.erstelleFahrzeug(...)), ohne new FahrzeugFactory() erstellen zu müssen.
    • Fahrzeug: Der Rückgabetyp ist das Fahrzeug-Interface. Das bedeutet, die Methode gibt irgendein Objekt zurück, das den Fahrzeug-Vertrag erfüllt.
    • String typ: Dies ist die Eingabe, z.B. “auto” oder “motorrad”.
  3. switch (typ.toLowerCase()): Die Methode nutzt eine switch-Anweisung (eine saubere Alternative zur if/elif-Kette), um den typ zu prüfen. toLowerCase() stellt sicher, dass “Auto” und “auto” gleich behandelt werden.
  4. case "auto": return new Auto();: Wenn der Typ “auto” ist, erstellt die Fabrik ein new Auto() und gibt es zurück.
  5. Vorteil: Der Rest des Programms muss die konkreten Klassen (Auto, Motorrad) nicht mehr kennen. Wenn ein neuer Fahrzeugtyp (z.B. LKW) hinzukommt, muss nur diese Factory-Klasse angepasst werden.

Praktisches Beispiel: Fahrzeug-Factory

4. Verwendung

public class FahrzeugApp {
    public static void main(String[] args) {
        // Erstellt ein Auto über die Factory
        Fahrzeug meinFahrzeug = FahrzeugFactory.erstelleFahrzeug("auto");
        meinFahrzeug.starten();
        meinFahrzeug.fahren();
 
        // Erstellt ein Motorrad über die Factory
        Fahrzeug zweitesFahrzeug = FahrzeugFactory.erstelleFahrzeug("motorrad");
        zweitesFahrzeug.starten();
        zweitesFahrzeug.fahren();
    }
}

Erklärung des Codes

Dieser Code (FahrzeugApp) zeigt, wie die FahrzeugFactory (aus dem vorigen Beispiel) von einem “Client” (dem Hauptprogramm) genutzt wird.

  1. Entkoppelte Erstellung: Das Hauptprogramm (main) ruft FahrzeugFactory.erstelleFahrzeug("auto") auf. Es sagt der Fabrik was es will (ein “auto”), aber es kümmert sich nicht darum, wie es erstellt wird (es ruft nicht new Auto() auf).
  2. Nutzung über Interface: Das zurückgegebene Objekt (ein Auto-Objekt) wird in einer Variable vom Typ Fahrzeug (dem Interface/Vertrag) gespeichert.
  3. Polymorphismus: Das Programm ruft meinFahrzeug.starten() auf. Obwohl meinFahrzeug nur als Fahrzeug bekannt ist, führt Java die spezifische starten()-Methode der Auto-Klasse aus.
  4. Flexibilität: Dasselbe passiert beim Erstellen des Motorrads. Das Hauptprogramm verwendet exakt denselben Aufrufstil, bekommt aber ein anderes Objekt mit anderem Verhalten (Motorrad-Implementierung).

Der FahrzeugApp-Code muss die konkreten Klassen Auto oder Motorrad nie kennen oder importieren. Er interagiert nur mit der FahrzeugFactory und dem Fahrzeug-Interface.

⏳ Lädt Dataview-Inhalt...

Vorteile des Fabrikmethoden-Musters

Das Fabrikmethoden-Muster bietet mehrere wesentliche Vorteile in der Softwareentwicklung, die vor allem auf die Prinzipien der Entkopplung und Flexibilität abzielen.

  1. Starke Entkopplung von Code Der größte Vorteil ist die Entkopplung. Der Client-Code (in unserem Beispiel die Anwendung-Klasse) arbeitet nur gegen das abstrakte Produkt-Interface (Dokument). Er muss die konkreten Implementierungen (WordDokument, PdfDokument) nicht kennen. Dadurch können die konkreten Produktklassen geändert oder ausgetauscht werden, ohne den Client-Code anpassen zu müssen.

  2. Einfache Erweiterbarkeit (Open/Closed-Prinzip) Das Muster folgt dem Open/Closed-Prinzip: Die Software ist offen für Erweiterungen, aber geschlossen für Modifikationen. Um einen neuen Produkttyp hinzuzufügen (z.B. ein ExcelDokument), müssen Sie lediglich eine neue Produktklasse und eine neue Erzeuger-Klasse (ExcelAnwendung) erstellen. Bestehender Code muss nicht verändert werden, was das Risiko von Fehlern in der bestehenden Funktionalität minimiert.

Vorteile des Fabrikmethoden-Musters

  1. Zentralisierung der Erstellungslogik Die Logik zur Erstellung eines Objekts ist an einem einzigen, klar definierten Ort gekapselt: in der Fabrikmethode der jeweiligen Erzeuger-Klasse. Wenn sich der Konstruktor eines Produkts ändert oder die Initialisierung komplexer wird, muss die Anpassung nur an dieser einen Stelle erfolgen. Dies verbessert die Wartbarkeit und verhindert doppelten Code.

  2. Kontrolle durch Unterklassen Das klassische Muster gibt den Unterklassen die volle Kontrolle darüber, welche Objekte erzeugt werden. Die abstrakte Oberklasse (Anwendung) legt den Rahmen und die allgemeine Logik fest (das “Wann” und “Wie” der Nutzung), aber die konkreten Unterklassen (WordAnwendung) entscheiden über das spezifische Objekt (das “Was”). Dies ist besonders in Frameworks nützlich, wo das Framework die Struktur vorgibt, die konkrete Implementierung aber dem Nutzer überlassen wird.

  3. Verbesserte Lesbarkeit und Wartbarkeit Indem die Erstellungslogik aus der allgemeinen Geschäftslogik entfernt wird, wird der Code sauberer und leichter verständlich. Statt new-Anweisungen im gesamten Code verteilt zu haben, gibt es klar benannte Methoden, deren einziger Zweck die Objekterzeugung ist.

Wann solltest du das Factory Method Pattern verwenden?

Damit du die Einsatzgebiete besser einschätzen kannst, zeigt die folgende Tabelle, wann das Pattern geeignet ist und wann nicht.

SituationEmpfehlung
Du musst verschiedene Varianten eines Objekttyps erzeugenVerwenden
Die Objekterstellung ist komplex oder benötigt viele ParameterVerwenden
Du möchtest neue Typen hinzufügen, ohne bestehenden Code zu ändernVerwenden
Die Auswahl des Objekttyps erfolgt zur LaufzeitVerwenden
Du möchtest für Tests Mock-Objekte einsetzenVerwenden
Es gibt nur einen einzigen ObjekttypNicht verwenden
Die Objekterstellung ist sehr einfachNicht verwenden
Du möchtest unnötige Abstraktion und Overengineering vermeidenNicht verwenden

Das klassische Fabrikmethoden-Muster (Factory Method)

Nachdem wir die “Einfache Fabrik” kennengelernt haben, folgt nun das klassische Fabrikmethoden-Entwurfsmuster, wie es ursprünglich von der “Gang of Four” definiert wurde.

Der entscheidende Unterschied liegt im Einsatz von Vererbung. Anstatt einer einzigen Klasse, die über einen Parameter entscheidet, definieren wir hier eine Hierarchie von Erzeuger-Klassen.

Die Struktur sieht wie folgt aus:

  • Eine abstrakte Erzeuger-Klasse (Creator) deklariert die abstrakte Fabrikmethode.
  • Konkrete Unterklassen (Concrete Creators) implementieren diese Methode und legen so fest, welches spezifische Produkt sie erzeugen.

Dieses Vorgehen überlässt die Verantwortung für die Objekterstellung vollständig den Unterklassen und bietet dadurch ein höheres Maß an Flexibilität, insbesondere in größeren Anwendungs-Frameworks. Das nachfolgende Beispiel einer Dokumenten-Anwendung verdeutlicht diesen Aufbau.

Praxisbeispiel: Fabrikmethode

1. Das Produkt-Interface

// Definiert die gemeinsamen Operationen für alle Dokumenttypen
public interface Dokument {
    void oeffnen();
    void speichern();
    void schliessen();
}

Erklärung des Codes

Dieser Java-Code definiert ein interface (eine Schnittstelle) namens Dokument.

Ein interface ist ein reiner Vertrag. Es legt fest, welche Methoden (Funktionen) eine Klasse bereitstellen muss, wenn sie diesen Vertrag “unterschreibt” (implementiert).

In diesem Fall legt der Vertrag Dokument fest, dass jede Klasse, die ein Dokument sein will (z.B. TextDokument oder PDFDokument), die folgenden drei Methoden implementieren muss:

  • void oeffnen()
  • void speichern()
  • void schliessen()

Praxisbeispiel: Fabrikmethode

2. Konkrete Produkte

// Konkrete Implementierung für ein Word-Dokument
public class WordDokument implements Dokument {
    @Override
    public void oeffnen() {
        System.out.println("Word-Dokument wird geöffnet...");
    }
 
    @Override
    public void speichern() {
        System.out.println("Word-Dokument wird als .docx gespeichert.");
    }
 
    @Override
    public void schliessen() {
        System.out.println("Word-Dokument wird geschlossen.");
    }
}
 
// Konkrete Implementierung für ein PDF-Dokument
public class PdfDokument implements Dokument {
    @Override
    public void oeffnen() {
        System.out.println("PDF-Dokument wird im Reader geöffnet...");
    }
 
    @Override
    public void speichern() {
        System.out.println("PDF-Dokument wird als .pdf gespeichert.");
    }
 
    @Override
    public void schliessen() {
        System.out.println("PDF-Dokument wird geschlossen.");
    }
}

Erklärung des Codes

Dieser Code zeigt zwei konkrete Implementierungen des Dokument-Vertrags (Interfaces) aus dem vorigen Beispiel.

  1. implements Dokument: Beide Klassen (WordDokument und PdfDokument) “unterschreiben” den Dokument-Vertrag. Das bedeutet, Java zwingt sie, alle im Interface definierten Methoden (oeffnen, speichern, schliessen) bereitzustellen.
  2. @Override: Diese Markierung (Annotation) signalisiert: “Die folgende Methode ist unsere spezifische Implementierung einer Methode, die vom Interface (oder einer Mutterklasse) verlangt wird.”
  3. Spezifisches Verhalten: Obwohl beide Klassen dieselben Methoden haben, ist der Code innerhalb der Methoden unterschiedlich. WordDokument.speichern() tut etwas anderes als PdfDokument.speichern(). Das ist der Kernzweck von Interfaces: Sie erzwingen ein einheitliches “Was” (alle müssen speichern können), erlauben aber ein unterschiedliches “Wie” (Word speichert als .docx, PDF als .pdf).

Praxisbeispiel: Fabrikmethode

3. Der abstrakte Erzeuger (Creator)

// Die abstrakte Creator-Klasse
public abstract class Anwendung {
 
    // Die eigentliche Fabrikmethode. Sie ist abstrakt.
    // Unterklassen MÜSSEN diese Methode implementieren, um das spezifische Produkt zu erstellen.
    protected abstract Dokument erstelleDokument();
 
    // Diese Methode nutzt die Fabrikmethode, um ein Dokument zu erstellen
    // und damit zu arbeiten. Die Logik hier ist für alle Dokumenttypen gleich.
    public void neuesDokument() {
        System.out.println("Anwendung: Starte Logik zur Dokumentenerstellung.");
        Dokument doc = erstelleDokument(); // Hier wird die Fabrikmethode aufgerufen
        doc.oeffnen();
        doc.speichern();
    }
}

Erklärung des Codes

Dieser Code zeigt das Factory Method Pattern (Fabrikmethode-Muster). Der Kern des Musters besteht darin, die Entscheidung, welches Objekt erstellt wird, an Unterklassen (Kindklassen) zu delegieren.

  1. public abstract class Anwendung: Dies ist eine abstrakte Klasse – eine unvollständige “Vorlage”. Man kann kein Anwendung-Objekt direkt erstellen. Sie dient als Basis für spezialisierte Anwendungen (z.B. eine WordAnwendung).
  2. protected abstract Dokument erstelleDokument():
    • Dies ist die “Factory-Methode”.
    • abstract: Die Methode hat keinen Code. Sie ist ein Zwang: Jede konkrete Kindklasse (z.B. WordAnwendung) muss diese Methode implementieren und definieren, welches Dokument sie erstellt.
  3. public void neuesDokument():
    • Dies ist eine konkrete Methode, die einen festen Arbeitsablauf definiert.
    • Sie ruft erstelleDokument() auf. Sie weiß nicht, welche Implementierung dieser Methode ausgeführt wird – nur, dass sie ein Dokument-Objekt zurückbekommt.
    • Der Clou: Wenn eine WordAnwendung diese Methode aufruft, wird WordAnwendung.erstelleDokument() ausgeführt. Wenn eine PdfAnwendung sie aufruft, wird PdfAnwendung.erstelleDokument() ausgeführt.
    • Der Rest der Logik (doc.oeffnen(), doc.speichern()) ist generisch und funktioniert mit jedem Dokument.

Praxisbeispiel: Fabrikmethode

4. Konkrete Erzeuger (Concrete Creators)

// Ein konkreter Erzeuger für Word-Dokumente
public class WordAnwendung extends Anwendung {
    @Override
    protected Dokument erstelleDokument() {
        // Diese Fabrik ist dafür verantwortlich, Word-Dokumente zu erstellen.
        return new WordDokument();
    }
}
 
// Ein konkreter Erzeuger für PDF-Dokumente
public class PdfAnwendung extends Anwendung {
    @Override
    protected Dokument erstelleDokument() {
        // Diese Fabrik ist dafür verantwortlich, PDF-Dokumente zu erstellen.
        return new PdfDokument();
    }
}

Erklärung des Codes

Dieser Code zeigt die konkreten Kindklassen, die die abstrakte Anwendung-Vorlage (aus dem vorigen Beispiel) mit Leben füllen.

  1. extends Anwendung: Sowohl WordAnwendung als auch PdfAnwendung erben von der Anwendung-Klasse.
  2. @Override protected Dokument erstelleDokument(): Weil die Anwendung-Klasse die Methode erstelleDokument() als abstract (Pflicht zur Implementierung) deklariert hat, müssen diese Kindklassen sie implementieren (anzezeigt durch @Override).
  3. Die Entscheidung (Der Kern des Musters): Hier wird nun die “Fabrikmethode” konkretisiert:
    • Die WordAnwendung entscheidet sich, ein new WordDokument() zurückzugeben.
    • Die PdfAnwendung entscheidet sich, ein new PdfDokument() zurückzugeben.

Der allgemeine Algorithmus (neuesDokument() in der Anwendung-Klasse) bleibt derselbe, aber die spezifische Klasse, die erstellt wird, wird von der jeweiligen Kindklasse (WordAnwendung oder PdfAnwendung) bestimmt.

Praxisbeispiel: Fabrikmethode

5. Verwendung (Client)

public class Client {
    public static void main(String[] args) {
        Anwendung meineAnwendung;
        String konfiguration = "word"; // Diese Einstellung könnte z.B. aus einer Datei kommen
 
        // Abhängig von der Konfiguration wird die passende Fabrik gewählt
        if (konfiguration.equalsIgnoreCase("word")) {
            meineAnwendung = new WordAnwendung();
        } else {
            meineAnwendung = new PdfAnwendung();
        }
 
        // Der Client-Code arbeitet nun mit der abstrakten Anwendung,
        // ohne die konkreten Dokumentenklassen zu kennen.
        meineAnwendung.neuesDokument();
 
        System.out.println("\n--- Konfiguration geändert ---");
 
        // Wechsel zur PDF-Anwendung
        meineAnwendung = new PdfAnwendung();
        meineAnwendung.neuesDokument();
    }
}

Erklärung des Codes

Dieser Client-Code (das Hauptprogramm) demonstriert, wie die Fabrikmethode (Factory Method) aus den vorigen Beispielen genutzt wird, um das Programm zur Laufzeit flexibel zu konfigurieren.

  1. Konfiguration: Der Code simuliert eine externe Einstellung (z.B. aus einer Konfigurationsdatei) mit der Variable konfiguration = "word".
  2. Auswahl der Fabrik: Der if-Block prüft diese Konfiguration. Er entscheidet, welche konkrete Fabrik (WordAnwendung oder PdfAnwendung) erstellt wird.
  3. Abstrakte Variable: Das erstellte Objekt (z.B. new WordAnwendung()) wird in der Variable meineAnwendung gespeichert. Wichtig ist, dass diese Variable den abstrakten Typ Anwendung hat.
  4. Entkoppelte Nutzung: Der Client ruft meineAnwendung.neuesDokument() auf. Dieser Aufruf ist generisch. Der Client-Code weiß nicht (und es ist ihm egal), ob meineAnwendung eine WordAnwendung oder PdfAnwendung ist. Er weiß nur, dass es eine Anwendung ist, die die Methode neuesDokument() hat.
  5. Das Ergebnis (Polymorphismus): Intern ruft neuesDokument() die (überschriebene) erstelleDokument()-Methode auf. Da meineAnwendung (im ersten Fall) ein WordAnwendung-Objekt ist, wird dessen erstelleDokument()-Methode ausgeführt, und ein WordDokument wird erstellt. Im zweiten Fall (PdfAnwendung) wird ein PdfDokument erstellt.

Herausforderungen

Mögliche Herausforderungen sind:

  • Komplexität: Mehr Klassen und zusätzlicher Code.
  • Overhead: Zusätzliche Abstraktionsebene.
  • Lernkurve: Einsteiger müssen das Pattern erst verstehen.
⏳ Lädt Dataview-Inhalt...

Zusammenfassung

Zusammenfassung:

Factory Method Pattern – Grundlagen

Das Factory Method Pattern gehört zur Kategorie der Erzeugungsmuster in der Softwareentwicklung. Es löst typische Probleme bei der Objekterstellung, indem es die Instanziierung in eine separate Fabrikmethode auslagert.

  • Der Client-Code muss nicht mehr wissen, wie Objekte erstellt werden.
  • Die Objekterstellung wird von der Nutzung entkoppelt.
  • Neue Typen lassen sich einfach hinzufügen, ohne den Client-Code anzupassen.
  • Der Code wird übersichtlicher, wartbarer und besser testbar.
  • Durch Factory-Methoden können aussagekräftige Namen verwendet werden, die den Code verständlicher machen.

Funktionsweise des Factory Method Patterns

Die Grundidee ist, Objekte nicht direkt mit new zu erstellen, sondern eine Factory-Methode aufzurufen, die das passende Objekt zurückgibt.

  • Produkt-Interface: Definiert die gemeinsamen Methoden, die alle Produkte implementieren.
  • Konkrete Produkte: Implementieren das Interface mit spezifischem Verhalten.
  • Factory-Klasse: Enthält die Fabrikmethode, die je nach Eingabe das passende Objekt erzeugt.
  • Verwendung im Client: Der Client-Code ruft nur noch die Factory auf und kennt die konkreten Klassen nicht.

Beispiel: Eine Fahrzeug-Factory erstellt verschiedene Fahrzeugtypen wie Auto, Motorrad oder Fahrrad. Der Client arbeitet dabei ausschließlich mit dem Interface Fahrzeug.

Nutzen und Einsatz des Factory Method Patterns

Das Pattern bietet klare Vorteile, hat aber auch Grenzen.

Vorteile:

  • Entkopplung des Client-Codes von konkreten Klassen.
  • Einfache Erweiterbarkeit durch Hinzufügen neuer Typen.
  • Verbesserte Wartbarkeit, da Änderungen zentral in der Factory erfolgen.
  • Verbesserte Testbarkeit durch einfache Erzeugung von Mock-Objekten.
  • Gesteigerte Lesbarkeit durch sprechende Methodennamen.

Nachteile:

  • Zusätzliche Komplexität durch mehr Klassen und Abstraktionsebenen.
  • Overhead, wenn die Objekterstellung sehr einfach wäre.
  • Lernkurve für Einsteiger, die das Pattern erst verstehen müssen.

Einsatzgebiete:

  • Sinnvoll bei komplexer Objekterstellung oder wenn mehrere Varianten eines Typs benötigt werden.
  • Geeignet, wenn neue Typen flexibel ergänzt werden sollen.
  • Praktisch für Tests, da Mock-Objekte einfach über die Factory eingebunden werden können.
  • Nicht geeignet bei sehr einfachen Szenarien oder wenn es nur einen einzigen Objekttyp gibt.

Ausblick:

In der nächsten Lerneinheit lernst du das Beobachtermuster (Observer Pattern) kennen – ein Entwurfsmuster, das es ermöglicht, dass Objekte automatisch über Änderungen anderer Objekte informiert werden. Du wirst verstehen, wie dieses Prinzip für reaktive und ereignisgesteuerte Systeme genutzt wird und warum es in modernen Anwendungen, etwa bei Benutzeroberflächen oder Event-Systemen, eine zentrale Rolle spielt.