Das Factory Method-Muster

Definition

Das Factory Method-Muster (Fabrikmethode) ist eines der GoFEntwurfsmuster (Design Pattern) und gehört zu der Kategorie der Erzeugungsmuster (Creational Design Pattern). Das Muster „definiert eine Schnittstelle zur Erstellung eines Objekts, lässt aber die Unterklassen entscheiden, welche Klassen instanziert werden. Factory Method ermöglicht einer Klasse, die Instanzierung in Unterklassen zu verlagern“ (EvKbF S. 134).

Beschreibung

Eines der wesentlichen Merkmale der objektorientierten Programmierung sind die Beziehungen bzw. Abhängigkeiten zwischen den Objekten. Ein Entwurf mit vielen Abhängigkeiten kann jedoch sehr schnell unflexibel und schwer wartbar werden. Eine vollständige Vermeidung von Abhängigkeiten ist nicht möglich. Das Ziel sollte daher sein die Abhängigkeiten in einem Entwurf auf ein Minimum zu reduzieren. Durch die Anwendung des folgenden OO-Entwurfsprinzips, ist das Factory Method-Muster eine Hilfe dieses Ziel zu erreichen:

„Stützen Sie sich auf Abstraktionen. Stützen Sie sich nicht auf konkrete Klassen.“
Das Abhängigkeits-Umkehrungs-Prinzip

Die Verwendung von Abstraktionen verleiht einem Entwurf Flexibilität. Neue Anforderungen lassen sich schnell integrieren, ohne dass der bestehende Code geändert werden muss. Welche Rolle das Factory Method-Muster dabei spielt, beschreibt das Buch „Entwurfsmuster von Kopf bis Fuß“ auf der Seite 125, wie folgt:

Eine Factory Method-Fabrikmethode kümmert sich um die Objekt-Erstellung und kapselt sie in einer Unterklasse. Damit wird der Client-Code in der Superklasse vom Objekt-Erstellungscode in der Unterklasse entkoppelt.

  • Eine Fabrikmethode kümmert sich um die Objekt-Erstellung und kapselt sie in einer Unterklasse. Das entkoppelt den Client-Code in der Superklasse von der Objekt-Erstellung in der Unterklasse.
  • Eine Fabrikmethode liefert ein Produkt zurück, das üblicherweise innerhalb der Methoden verwendet wird, die in der Superklasse definiert werden.
  • Eine Fabrikmethode isoliert den Client (den Code in der Superklasse) so, dass er nicht wissen muss, welche konkrete Art von Produkt tatsächlich erstellt wird.
  • Eine Fabrikmethode kann so parametrisiert sein (oder nicht), dass sie unter verschiedenen Formen eines Produkts auswählt.

Die Kapselung der Objekt-Erstellung in einer Klasse bietet die Möglichkeit, diese in verschiedenen Clients wiederzuverwenden. Gleichzeitig hat man auch nur eine einzige Stelle, an der man Änderungen durchführen muss, sollte sich die Implementierung ändern.

Beispiel

Die Verwendung des Factory Method-Musters wird am Beispiel der Implementierung einer Pizzeria deutlich. Im ersten Schritt untersuchen wir die abstrakten Aspekte des Entwurfs, Aspekte die alle Pizzerias gemein haben. In dem Beispiel wären diese allgemeinen Aspekte der Bestellprozess und die Herstellung einer Pizza. Die Implementierung könnte wie folgt aussehen.

public abstract class PizzaStore
{
	public Pizza OrderPizza(string type)
	{
		Pizza pizza = this.CreatePizza(type);
		pizza.Prepare();
		pizza.Bake();
		pizza.Cut();
		pizza.Box();

		return pizza;
	}

	protected abstract Pizza CreatePizza(string type);
}

public abstract class Pizza
{
	public string Name { get; set; }

	public void Prepare()
	{
		Console.WriteLine("Prepare " + this.Name);
	}

	public void Bake()
	{
		Console.WriteLine("Bake " + this.Name);
	}

	public void Cut()
	{
		Console.WriteLine("Cut " + this.Name);
	}

	public void Box()
	{
		Console.WriteLine("Box " + this.Name);
	}

	public override string ToString()
	{
		return this.Name;
	}
}

Da der Bestellprozess in allen Pizzerias gleich ist, wurde die OrderPizza() Methode in der abstrakten PizzaShop Klasse vollständig implementiert. Die CreatePizza() Methode wurde dagegen als abstract deklariert, da die konkreten Pizzerias unterschiedliche Pizzas herstellen können. Dieser Aspekt ist also nicht allgemeingültig.

Nach der Definition der Abstraktion des Entwurfs folgt die Implementierung der konkreten Klassen. Dabei dienen die zuvor erstellten abstrakten Klassen als Schnittstellen oder Superklassen, von den sich die konkreten Klassen ableiten.

public class NewYorkPizzaStore : PizzaStore
{
	protected override Pizza CreatePizza(string type)
	{
		Pizza pizza = null;

		switch (type)
		{
			case PizzaType.CHEESE:
				pizza = new CheesePizza();
				pizza.Name = "New York Cheese Pizza";
				break;

			case PizzaType.CLAM:
				pizza = new ClamPizza();
				pizza.Name = "New York Clam Pizza";
				break;

			case PizzaType.PEPPERONI:
				pizza = new PepperoniPizza();
				pizza.Name = "New York Pepperoni Pizza";
				break;

			case PizzaType.VEGGIE:
				pizza = new VeggiePizza();
				pizza.Name = "New York Veggie Pizza";
				break;

			default:
				throw new ArgumentException(String.Format("Unkonown Pizza Type '{0}'", type));
		}

		return pizza;
	}
}

public class CheesePizza : Pizza
{ ... }

public class PepperoniPizza : Pizza
{ ... }

Die abstrakte Methode CreatePizza() wird nun in der konkreten NewYorkPizzaStore Klasse implementiert. Damit delegiert die Oberklasse die Objektinstanziierung an die Unterklasse, und die Fabrikmethode CreatePizza() entscheidet entsprechend des übergebenen Parameters type, welche konkrete Klasse instanziiert wird.

Hinweis: In dem Beispiel ist der Parameter type vom Typ string. Um mehr Typsicherheit zu erhalten, empfiehlt es sich die Parameterwerte als Konstanten in einer statischen Klasse zu implementieren oder den Parameterwert als Enum zu deklarieren. Der Vorteil besteht darin, dass ein Entwickler weiß, welcher Wert übergeben werden muss und auch beim etwaigen Refactoring Fehler vermieden werden.

In einem Client wird der Entwurf dann wie folgt angewendet.

static void Main(string[] args)
{
	PizzaStore pizzaStore = new NewYorkPizzaStore();

	Pizza pizza = pizzaStore.OrderPizza(PizzaType.CHEESE);
	Console.WriteLine(pizza.ToString());

	pizza = pizzaStore.OrderPizza(PizzaType.PEPPERONI);
	Console.WriteLine(pizza.ToString());

	Console.ReadLine();
}

Fazit

Die Implementierung des Factory Method-Musters wäre somit vollständig. Man kann hierbei die Vererbungshierarchie gut erkennen. Der Client stützt sich auf die abstrakten Klassen PizzaShop und Pizza und muss die konkreten Klassen nicht kennen. Daraus ergeben sich folgende Vorteile:

  • Wiederverwendbarkeit
    Durch die Abstraktion der Objektinstanziierung wird die Objektverarbeitung entkoppelt, womit jedes Objekt verarbeitet werden kann, welches die entsprechende Schnittstelle implementiert. Neue Objekte können schnell integriert werden, da der Verarbeitungscode wiederverwendet werden kann.
  • Erweiterbarkeit
    Neue konkrete Klassen können schnell integriert werden, ohne dass der Code geändert werden muss. Die neuen konkreten Klassen müssen lediglich die entsprechende Schnittstelle implementieren. Das Open/Closed Prinzip bleibt somit gewahrt.
  • Konsistenz
    Die Objektverarbeitung wird zentralisiert und kann an einer Stelle erweitert und gewartet werden. Zudem wird die Verarbeitung des Objektes sichergestellt.

UML

Factory Method Design Pattern UML Diagram

Akteure

  • Product (Produkt)
    Definiert eine Schnittstelle für ein Produkt.
  • ConcreteProduct (KonkretesProdukt)
    Repräsentiert ein konkretes Produkt durch die Implementierung der Schnittstelle.
  • Creator (Erzeuger)
    Definiert eine Fabrikmethode zur Erzeugung der Produkte. Die Fabrikmethode kann eine Default-Implementierung zur Erzeugung eines Default-Produktes enthalten.
  • ConcreteCreator (KonkreterErzeuger)
    Erzeugt konkrete Produkte durch das Überschreiben der Fabrikmethode.

Quellen

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.