Die Fabrikmethode (engl. Factory Method) ist ein Erzeugungsmuster, das beschreibt, wie ein Objekt durch den Aufruf einer Methode anstatt eines Konstruktors erzeugt wird. Diese Methode ist Teil einer sogenannten Fabrik-Klasse, die für die Erzeugung von Objekten zuständig ist. Es ist irreführend, dass die Fabrikmethode im allgemeinen Sprachgebrauch von Software-Entwicklern sowohl eine beliebige statische Methode zur Objekterzeugung beschreibt als auch eines der ursprünglichen GoF-Entwurfsmuster.

Statische Fabrikmethode

Wir stellen hier zunächst die statische Fabrikmethode vor, die von Joshua Bloch in seinem bekannten Java-Lehrbuch "Effective Java" [Blo17] wie folgt definiert:

"A class can provide a public static factory method, which is simply a static method that returns an instance of the class." (Joshua Bloch)

Das folgende Code-Beispiel illustriert die statische Fabrikmethode anhand der Klasse LoggerFactory, die hier zwei unterschiedliche Logger (nämlich einen ConsoleLogger und einen FileLogger) erzeugen kann (Zeilen 5-12). Über ein Argument wird beispielhaft entschieden, welcher konkrete Logger erzeugt wird. Der ConsoleLogger ist hier der Default, falls die Methode createLogger ohne Argument aufgerufen wird (Zeilen 14-16). Dieser Default könnte auch aus einer Konfigurationsdatei eingelesen oder per Dependency Injection verknüpft werden.

public class LoggerFactory {

    public enum LogType {CONSOLE, FILE};

    public static MyLogger createLogger(LogType type) {
        if (type.equals(LogType.CONSOLE)) {
            return new ConsoleLogger();
        } else if (type.equals(LogType.FILE)) {
            return new FileLogger();
        }
        return null;
    }

    public static MyLogger createLogger() {
		return createLogger(LogType.CONSOLE); // default logger
    }
}
interface MyLogger {
    void log(String message);
    default void close() {}
}
public class ConsoleLogger implements MyLogger {
	
    @Override
    public void log(String message) {
        System.out.println(message);
    }
}
public class FileLogger implements MyLogger {

    BufferedWriter writer;

    @Override
    public void log(String message) {
        try {
            if (writer == null) {
                writer = new BufferedWriter(new FileWriter("log.txt", true));
            }
            writer.append(message + "\n");
        } catch (IOException e) {}
    }

    @Override
    public void close() {
        try {
            writer.close();
        } catch (IOException e) {}
    }
}
class Client {

    public static void main(String[] args) {
        MyLogger a = new ConsoleLogger(); // constructor instantiation >> causes dependency to ConsoleLogger
        a.log("Hello World!");

        MyLogger b = LoggerFactory.createLogger(); // static factory method instantiation
        b.log("Hello World!");
    }
}

Das Interface MyLogger schreibt die gemeinsame Schnittstelle für alle konkreten Logger vor, die diese individuell implementieren. Der Client ist – im Gegensatz zur einfachen Instantiierung eines Loggers per Konstruktor – bei Verwendung der Fabrikmethode nicht mehr von einer konkreten Logger-Klasse abhängig, sondern kann ausschließlich auf dem Interface MyLogger arbeiten.

Die Stärke dieses Musters liegt in seiner Einfachheit. Im Vergleich zu dem GoF-Entwurfsmuster Fabrikmethode (s. unten) muss die Fabrik-Klasse nicht ihrerseits instanziiert werden, um Objekte zu erzeugen, und für die erzeugende Methode muss kein Interface definiert werden. Eine statische Fabrikmethode setzt natürlich voraus, dass die Programmiersprache Klassenmethoden (≙ Schlüsselwort static in Java) kennt.

Die statische Fabrikmethode kann auch Gebrauch von der Java Reflection API machen, was in folgendem Code-Beispiel dargestellt ist (Zeile 4).

public class ReflectionLoggerFactory {

    public static MyLogger createLogger(Class<? extends MyLogger> loggerClass) {
        return loggerClass.getConstructor().newInstance(); // create instance by default constructor reflection
    }

    public static MyLogger createLogger() {
        return createLogger(ConsoleLogger.class); // default logger
    }	
}

Auch die wirkliche Java Logging API setzt übrigens auf eine statische Fabrikmethode getLogger in der Klasse java.util.logging.Logger, um ein Logger-Objekt zu erzeugen:

import java.util.logging.Logger;
// ...
Logger logger = Logger.getLogger(getClass().getName());
logger.log(Level.INFO, "Hello World!");

Entwurfsmuster Fabrikmethode

Nun möchten wir die bisherige statische Fabrikmethode zu dem GoF-Entwurfsmuster Fabrikmethode erweitern. Das Ziel des Musters ist es, dass neue Klassen zu einer bestehenden Schnittstelle oder Vererbungshierarchie durch Dritte hinzugefügt werden können, und Objekte dieser neuen Klassen über eine zugehörige Fabrikmethode erzeugt werden können. Wir können uns z.B. vorstellen, dass ein Dritter einen weiteren Logger für das obige Interface MyLogger implementieren möchte, z.B. einen JDBCLogger oder einen RedisLogger, um das Log in einer Datenbank abzulegen. Der Dritte hat aber keinen Zugriff auf unseren Code, insbesondere nicht auf die Klasse LoggerFactory. Demzufolge muss die LoggerFactory ihrerseits von außen erweiterbar sein. Eine Lösung könnte wie folgt aussehen, wobei die bisherige Klasse LoggerFactory in LoggerCreator umbenannt wird:

abstract class LoggerCreator {

    Set<MyLogger> loggers;

    abstract MyLogger createLogger();

    MyLogger register() {
        MyLogger logger = createLogger();
        loggers.add(logger);
        return logger;
    }
}
class ConsoleLoggerCreator extends LoggerCreator {

    @Override
    MyLogger createLogger() { return new ConsoleLogger(); }
}
class Client {

    public static void main(String[] args) {
        LoggerCreator creator = new ConsoleLoggerCreator();

        MyLogger a = creator.register();
        a.log("Hello World!");

        MyLogger b = creator.register();
        b.log("Hello World!");

        System.out.println("Created "+ creator.loggers.size() + " loggers until now.");
    }
}

Die abstrakte Klasse LoggerCreator definiert eine abstrakte Methode createLogger. Eine statische Methode zur Erzeugung der bekannten konkreten Logger gibt es hingegen nicht mehr. Es wird angenommen, dass zukünftig verschiedene konkrete Logger (wie z.B. der JDBCLogger) entstehen, die durch Dritte realisiert werden. Der Fokus liegt auf der Erweiterbarkeit. Die Oberklasse zur Objekterzeugung LoggerCreator kann auch Methoden beinhalten, die Verhalten implementieren, das für alle Logger gleich ist – hier beispielhaft die Methode register. Nur die konkreten Erzeuger (z.B. JDBCLoggerCreator) sind von einer zugehörigen Logger-Implementierung (z.B. JDBCLogger) abhängig.

Das folgende UML-Klassendiagramm verallgemeinert das Logger-Beispiel, sodass das allgemeingültige Muster der GoF-Fabrikmethode entsteht, das durch Erich Gamma et al. [GHJ+10] wie folgt definiert ist:

"Define an interface for creating an object, but let subclasses decide which class to instantiate. The factory method lets a class defer instantiation it uses to subclasses." (Erich Gamma et al.)

In diesem Muster gibt es eine allgemeine Oberklasse (oder Schnittstelle) zur Erstellung von ähnlichen Objekten (Creator). Den konkreten Erzeugern wird eine Fabrik-Methode (factoryMethod) zur Objekterzeugung vorgeschrieben. Die Oberklasse hat keine Abhängigkeit zu einem konkreten Erzeuger oder konkreten Produkt. Dieses Muster ist insbesondere dann sinnvoll, wenn die Oberklasse eine weitere Methode enthält, in der sie Verhalten implementiert, das unabhängig vom konkreten Typ eines Produkts für jedes Produkt gleichermaßen gilt. Im obigen UML-Klassendiagramm ist dies die Methode anyOperation. Aus objektorientierter Sicht gehört dieses gemeinsame Verhalten eigentlich eher zur Schnittstelle Product als zur Fabrik-Klasse. Es kann aber in der Praxis sein, dass die Fabrik-Klasse zusätzlich fachliches Verhalten für die Produkte implementiert, da letztere auf einfache Modellklassen reduziert sind, die über ihre Attribute nur Zustand erfassen, aber keine fachlichen Methoden enthalten sollen.

Der Nachteil des Entwurfsmusters Fabrikmethode ist, dass zwei parallele Spezialisierungshierarchien gepflegt werden müssen. Das ist der Preis für die Erweiterbarkeit durch Dritte. Im Gegensatz dazu ist eine statische Fabrikmethode immer dann als einfacherer Ansatz zu bevorzugen, wenn alle Produktklassen vorab bekannt sind oder die Erweiterbarkeit durch Dritte nicht wichtig ist.

Wenn die zu erzeugenden Produkte sich in den notwendigen Argumenten beim Konstruktoraufruf unterscheiden und sich daher zur Instantiierung nicht auf eine gemeinsame Schnittstelle einigen können, kann das Entwurfsmuster Erbauer (engl. Builder) herangezogen werden, ggf. in Kombination mit der Fabrikmethode.

Abstrakte Fabrik

Wenn die Fabrik-Klasse nicht nur ein Produkt, sondern gleich eine ganze Familie von zusammengehörigen Produkten erzeugen soll, wird die Fabrikmethode schnell zum Entwurfsmuster Abstrakte Fabrik (engl. Abstract Factory) ausgebaut. Ein beliebtes Beispiel ist hier im Kontext von UI-Frameworks die Erzeugung von UI-Elementen (wie Buttons, Textfelder, usw.), die jeweils einem bestimmten Design-Stil folgen sollen (wie Cupertino oder Material Design). Hierbei entsteht für jedes Produkt einer Produktfamilie eine separate Spezialisierungshierarchie. Die konkreten Produkte einer Familie werden alle aus einer gemeinsamen Fabrik-Klasse erzeugt, die dadurch einen konsistenten Design-Stil gewährleistet. Der Client arbeitet ausschließlich auf abstrakten Produkten, die er über eine konkrete Fabrik-Klasse erzeugt. Die Idee der Abstrakten Fabrik wird durch das folgende UML-Klassendiagramm und das zugehörige Code-Beispiel verdeutlicht.

interface AbstractFactory {
    Button createButton();
    TextField createTextField();
}

interface Button {} // abstract product
interface TextField {} // another abstract product
class MaterialFactory implements AbstractFactory {

    @Override
    public Button createButton() { return new MaterialButton(); }

    @Override
    public TextField createTextField() { return new MaterialTextField(); }
}

class MaterialButton implements Button {} // concrete product
class MaterialTextField implements TextField {} // another concrete product
AbstractFactory factory = new MaterialFactory();
Button button = factory.createButton();
TextField textField = factory.createTextField();

Die gezeigten Code-Beispiele zum Entwurfsmuster Fabrikmethode finden sich im Verzeichnis /patterns/factory-method des Modul-Repository.