Factory method

Autor: Sergej Kukshaus Veröffentlicht: vor 7 monaten Lesedauer: ca. 4 Minuten

Kennst du das?

Du kannst dich einfach nicht entscheiden, was du genau haben möchtest. Manchmal willst du dich auch nicht entscheiden und diese Entscheidung lieber jemandem anderen überlassen.

Wir Softwareentwickler sind doch schon komische und teilweise auch bequeme Wesen, meinst du nicht auch? 🙂

Wenn du mal wieder vor solch einer „schweren“ Entscheidung stehst, denk daran. Du bist nicht allein.

Und wenn du glaubst, es handle sich um ein Luxus-Problem: weit gefehlt!

Factory method: Problem

In der Tat stehen wir sehr häufig vor dem Problem, dass wir mehr als eine Implementierung benötigen.

Es geht schon los, wenn man dem User die Möglichkeit geben möchte zu entscheiden, welche Aktion durchgeführt werden soll.

Klassiker ist das Ausgabeformat. Möchte der Benutzer in PDF oder Word speichern?

Ohne die Fabrikmethode sähe der Quellcode ungefähr so aus:

if (saveFileFormat == "PDF")
{
	PdfDocument pdf = new PdfDocument();
	pdf.Open();
	
	// ganz viel Code um die Datei zu generieren
	
	pdf.Save();
} 
else if (saveFileFormat == "Word") 
{
	WordDocument doc = new WordDocument();
	doc.Open();
	
	// ganz viel Code um die Datei zu generieren
	
	doc.Save();
}

Und dann gibt man dem Benutzer noch die Möglichkeit in das ODT-Format (OpenOffice & LibreOffice Format) zu schreiben. Dann kommt da noch ein if dazu … und noch eins … und noch eins …

Meistens gibt es für genau solche Methode auch keine UnitTests mehr.

Luxusproblem? Nein! Die längste Methode, die ich je gesehen habe, die mit diesem Pattern erschlagen hätte werden können, Betrug um die 10.000 Zeilen. Um nicht vollkommen die Übersicht zu verlieren, wurden da sogar „regions“ verwendet. Dies ist jedoch nur ein C#-Luxus.

Am Ende hat sich auch kein Entwickler mehr an diese Methode ran gewagt. Zu Groß war die Angst hier irgendwelche Fehler zu machen. Die Methode war absolut unwartbar.

Doch wie optimiert man die obere Methode? Dazu später mehr 🙂

Factory method: Definition

Define an interface for creating an object, but let subclasses decide which class to instantiate. Factory Method lets a class defer instantiation to subclasses.

Gang of four (Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides)

Mit anderen Worten: Es wird ein Interface bzw. eine „abstrakte Klasse“ definiert und mehrere „konkrete Klassen“.

Die Fabrikmethode kümmert sich um die Instanziierung und der Verwender um die korrekte Implementierung, welche dann mit allen Klassen gleichermaßen funktioniert.

Klingt doch toll, oder? 🙂

Factory method: Struktur

AbstractProduct: Beschreibt ein Produkt, welches später erzeugt werden soll

ConcreteProduct: Definiert Produkte mit bereits konkreter Implementierungslogik.

ConcreteFactory: Erzeugt Produkte, auf Basis der Anfrage aus dem Klienten.

Client: Erzeugt die Fabrik und verwendet dessen Methode um mit den konkreten Produkten weiterzuarbeiten. Im Folgenden ist es unerheblich, welches konkrete Produkt es sich im Endeffekt handelt.

Factory method: Real-World

Auch wenn man die Fabrikmethode relativ häufig verwendet, gibt es in der Realität kaum gute und geeignete Beispiele dafür.

Im Grunde genommen, stellt sich hier immer die Frage: „Was möchte ich haben und wie komme ich dran?„.

Sagen wir mal du möchtest einen guten Artikel über „Design Pattern“ lesen und befragst nach alter Manier, google. Dabei ist dir zunächst egal, auf welche Website du genau kommst. Schließlich möchtest du in diesem Augenblick nur das Ergebnis haben. Auch möchtest du den Artikel nicht erst schreiben, bevor du ihn lesen kannst.

Denk doch mal an das einkaufen. Du gehst auf den Wochenmarkt und möchtest ein Kilo Äpfel. Du bekommst auch Äpfel. Es ist aber zunächst nicht relevant, um welche Äpfel es sich hierbei handelt.

Bestimmt hast du auch schon mal etwas im Internet eingekauft. OK, hier steht zwar schon konkret fest, was du haben möchtest. Aber weißt du auch, wie es bei dir ankommt? Nein, das ist auch egal, ob es per Spedition, Kurier oder auch den gelb/roten Engeln gebracht wird. Hauptsache es kommt überhaupt an 🙂

Factory method: Pseudocode

// Beschreibt ein beliebiges Objekt, welches dynamisch und zur Laufzeit
// erzeugt werden soll.
interface Product

// Für jedes Produkt muss mindestens eine konkrete Klassen definiert werden, welche
// anschließend in der Fabrik erstellt werden.
class ConcreteProductA implements Product
class ConcreteProductB implements Product

// Die Produkte sollen im besten Fall nur mithilfe der Fabrik erstellt werden.
class ProductFactory
	// Abhängig davon was angefragt wird, werden verschiedene Produkte
	// erzeugt. Anschließend bekommt man eine implementierung für genau
	// diese Anfrage
	method create(which) : Product
		if which is A
			return new ConcreteProductA()
		if which is B
			return new ConcreteProductB()

program Client
	// innerhalb des Clients, muss nur noch die Fabrik und
	// mit der Create-Methode das passende Objekt erzeugt werden
	var factory = new ProductFactory()
	Product a = factory.create(A);
	Product c = factory.create(B);

Factory method vs. Abstract Factory

Bevor es ein wenig konkreter wird, möchte ich noch einmal erwähnen, dass die Fabrikmethode quasi der kleine Bruder der abstrakten Fabrik ist.

Beide Muster stehen nicht in Konkurenz zueinander. Sie können sogar sehr gut miteinander eingesetzt werden. Es kann auch durchaus sinnvoll sein, beide Muster zu vermischen.

Dan würde das jedoch wie folgt aussehen:

class MyFactory
{
	public IProduct GetProductA() => new ConcreteProductA();
	public IProduct GetProductB() => new ConcreteProductB();
}

Der Klassiker wäre dann sowas hier:

class MyFactory
{
	public IProduct GetProduct(string which)
	{
		switch(which) 
		{
			case "A": return new ConcreteProductA();
			case "B": return new ConcreteProductB();
		}
		throw new ProductNotAvailableException()
	}
}

Dieser ist zwar länger, bedenke aber, dass das der Parameter direkt aus der GUI oder auch aus der Konfigurationsdatei kommen kann. Bei der ersten Version bräuchtest du dafür trotzdem ein switch.

Wie immer, kommt es hier auf den Anwendungsfall an. Es ist aber von Vorteil zu wissen, dass man es auch so lösen kann 🙂

Factory method: Code Beispiel

Die Code-Beispiele sind jeweils bei github zu finden.

Das könnte dir auch gefallen