Ein Softwareshop verkauft vorranging Microsoft Office Applikationen, wie Word, Excel und Powerpoint. Wenn ein Kunde eine solche Applikation ordert, so sieht der entsprechende Code in der Verwaltungssoftware so aus:
Codeausschnitt aus der Verwaltungssoftware:
public class SoftwareShop {
public OfficeProgramm holeApp(String pZuHolendesProg) {
OfficeProgramm programm = null;
//Auswahl der benötigten Applikation
if (pZuHolendesProg.equals("Word")) {
programm = new Word();
}
else if (pZuHolendesProg.equals("Powerpoint")) {
programm = new Powerpoint();
}
else if (pZuHolendesProg.equals("Excel")) {
programm = new Excel();
}
else {
System.err.println("Ungültig!");
}
//Weitere Verarbeitung
programm.einpacken();
programm.etikettieren();
return programm;
}
}
Schnittstelle OfficeProgramm und die Implementierungen Word, Powerpoint und Excel:
abstract class OfficeProgramm {
public void einpacken() {}
public void etikettieren() {}
public abstract void starten();
}
class Word extends OfficeProgramm {
public void starten() {
System.out.println("Word startet");
}
}
class Powerpoint extends OfficeProgramm {
public void starten() {
System.out.println("Powerpoint startet");
}
}
class Excel extends OfficeProgramm {
public void starten() {
System.out.println("Excel startet");
}
}
Es wird die gewünschte Software ausgewählt, instanziiert, weiter bearbeitet (einpacken(), etikettieren()) und schließlich zurückgegeben. Dieser Entwurf hat allerdings eine Reihe von Nachteilen:
Um unseren Entwurf zu verbessern, rufen wir uns zwei alt bekannte OO-Entwurfsprinzipien ins Gedächtnis:
Offen/Geschlossen-Prinzip (Open/Closed):Gemeint ist, dass Erweiterungen (neue OfficeProgramme etc.) ohne Änderungen an bestehenden Code in das System integriert werden können. Dies im Hinterkopf behaltend erinnern wir uns an ein weiteres stets aktuelles OO-Prinzip:
Identifiziere jene Aspekte, die sich ändern und trenne sie von jenen, die konstant bleiben.
Veränderlich ist die Instanziierung von OfficeProgrammen. Konstant bleibt der weitere Verarbeitungsprozess danach (einpacken() und etikettieren()). Also liegt es nahe, die Logik zur OfficeProgrammerstellung in einer separaten Klasse zu kapseln - einer Factory.

SoftwareShop:
class SoftwareShop {
OfficeProgramFactory officeProgFactory = new OfficeProgramFactory();
public OfficeProgram holeApp(String pZuHolendesProg) {
//Logik zur Auswahl des gewünschten Programms in Factory gekapselt.
OfficeProgram program = officeProgFactory.createOfficeProgram(pZuHolendesProg);
//weitere verarbeitung
program.einpacken();
program.etikettieren();
return program;
}
}
OfficeProgramFactory:
class OfficeProgramFactory {
public OfficeProgram createOfficeProgram(String pZuHolendesProg) {
if (pZuHolendesProg.equals("Word")) {
return new Word();
}
else if (pZuHolendesProg.equals("Powerpoint")) {
return new Powerpoint();
}
else if (pZuHolendesProg.equals("Excel")) {
return new Excel();
}
else {
System.err.println("Ungültig!");
return null;
}
}
}
Die Factory - üblicherweise mit statischen Methoden - macht alleine aber noch kein Design Pattern. So nützlich sie auch ist, es handelt sich lediglich um ein Idiom. Nichtsdestotrotz haben wir nun eine Reihe von Missständen im alten Entwurf abgestellt.
Doch was ist mit unserem Offen/Geschlossen-Prinzip? Sind wir nun für Erweiterungen offen? Was ist, wenn unser SoftwareShop nun noch weitere Office Suites neben Microsoft Office im Sortiment führen soll, beispielsweise Apple iWork oder Sun OpenOffice?
Unser Praktikant schlägt einen ungünstigen Entwurf vor: Dieser sieht für jede Suite eine eigene Factory vor, die durch den SoftwareShop genutzt wird.
Schon nicht schlecht, allerdings wird nun ein entscheidener Nachteil unseres Entwurfs deutlich: Die Objekteverarbeitung und die Objekterstellung sind nun zu stark entkoppelt! Denn nun ist es möglich die (public) Methode createOfficeProgramm() von der MicrosoftOfficeFactory (oder einer anderen Factory) direkt aufzurufen, und damit die notwendigen Verarbeitungsschritte der holeApp()-Methode des SoftwareShops zu umgehen. So kann Software unverpackt und unetikettiert an die Umwelt gelangen. Maximale Entkopplung ist hier nicht sinnvoll.
Wie können wir auf der einen Seite die Verarbeitung von der Herstellung trennen, sie aber trotzdem aneinander koppeln? Die Lösung liegt in der Vererbung und der Verwendung einer abstrakten Methode, die eine Subklasse implementieren muss. Diese abstrakte Methode ist die namensgebende Factory Method.
Dabei fungiert unserer SoftwareShop als abstrakte Superklasse. Sie enthält nun zwei Methoden: Zum einen die bekannte holeApp() und zum anderen definiert nun sie (nicht die Factorys) die Methode createOfficeProgram(), allerdings abstrakt. Diese Methode wird mit den Zugriffsmodifier protected der Umwelt unzugänglich gemacht.
Abstrakte Superklasse SoftwareShop:
public abstract class SoftwareShop {
public OfficeProgramm holeApp(String pZuHolendesProg) {
//Delegation der Objekterstellung an Subklasse
OfficeProgramm programm = createOfficeProgram(pZuHolendesProg);
//weitere verarbeitung
programm.einpacken();
programm.etikettieren();
return programm;
}
//Definition der Factory Method
protected abstract OfficeProgramm createOfficeProgram(String pZuHolendesProg);
}
Der SoftwareShop hat keine Ahnung, welches konkrete Programm er erhält. Dies entscheidet allein die Subklasse, denn diese muss die Factory Method createOfficeProgram() implementieren. Der SoftwareShop delegiert die Objektinstanziierung an seine Subklasse. Von außen ist diese Methode unsichtbar (Zugriff protected). Somit wird gewährleistet, dass der Verarbeitungsprozess immer durchgeführt wird.
Die Subklassen erweitern SoftwareShop und implementieren die Factory Method, wobei sie natürlich ihre speziellen Programme je nach Hersteller zurückgeben.
Die Subklassen MicrosoftOfficeFactory, AppleiWorkFactory, SunOpenOfficeFactory instanziieren ihre Programme und geben sie zurück:
class MicrosoftOfficeFactory extends SoftwareShop{
@Override
protected OfficeProgramm createOfficeProgram(String pZuHolendesProg) {
OfficeProgramm programm = null;
if (pZuHolendesProg.equals("Textverarbeitung")) {
programm = new Word();
}
else if (pZuHolendesProg.equals("Präsentation")) {
programm = new Powerpoint();
}
else if (pZuHolendesProg.equals("Tabellenkalkulation")) {
programm = new Excel();
}
else {
System.err.println("Ungültig!");
}
return programm;
}
}
class AppleiWorkFactory extends SoftwareShop{
@Override
protected OfficeProgramm createOfficeProgram(String pZuHolendesProg) {
OfficeProgramm programm = null;
if (pZuHolendesProg.equals("Textverarbeitung")) {
programm = new Pages();
}
else if (pZuHolendesProg.equals("Präsentation")) {
programm = new Keynode();
}
else if (pZuHolendesProg.equals("Tabellenkalkulation")) {
programm = new Numbers();
}
else {
System.err.println("Ungültig!");
}
return programm;
}
}
class SunOpenOfficeFactory extends SoftwareShop{
@Override
protected OfficeProgramm createOfficeProgram(String pZuHolendesProg) {
OfficeProgramm programm = null;
if (pZuHolendesProg.equals("Textverarbeitung")) {
programm = new Write();
}
else if (pZuHolendesProg.equals("Präsentation")) {
programm = new Impress();
}
else if (pZuHolendesProg.equals("Tabellenkalkulation")) {
programm = new Calc();
}
else {
System.err.println("Ungültig!");
}
return programm;
}
}
Die OfficeProgramme der verschiedenen Hersteller:
abstract class OfficeProgramm {
public void einpacken() {}
public void etikettieren() {}
public abstract void starten();
}
class Word extends OfficeProgramm {
public void starten() {
System.out.println("Word startet");
}
}
class Powerpoint extends OfficeProgramm {
public void starten() {
System.out.println("Powerpoint startet");
}
}
class Excel extends OfficeProgramm {
public void starten() {
System.out.println("Excel startet");
}
}
class Pages extends OfficeProgramm {
public void starten() {
System.out.println("Pages startet");
}
}
class Keynode extends OfficeProgramm {
public void starten() {
System.out.println("Keynode startet");
}
}
class Numbers extends OfficeProgramm {
public void starten() {
System.out.println("Numbers startet");
}
}
class Write extends OfficeProgramm {
public void starten() {
System.out.println("Write startet");
}
}
class Impress extends OfficeProgramm {
public void starten() {
System.out.println("Impress startet");
}
}
class Calc extends OfficeProgramm {
public void starten() {
System.out.println("Calc startet");
}
}
Der Client instanziiert die gewünschte HerstellerFactory und kann sich von dieser fortan Programme über die Methode der Superklasse holen. Er arbeitet nur gegen die Schnittstellen (SoftwareShop, OfficeProgramm) und weiß damit nicht, mit welchen konkreten Produkten er arbeitet.
Beispielclient:
public class Client{
public static void main(String[] args) {
SoftwareShop appleShop = new AppleiWorkFactory();
OfficeProgramm appleTextProgram = appleShop.holeApp("Textverarbeitung");
appleTextProgram.starten();//"Pages startet"
SoftwareShop msShop = new MicrosoftOfficeFactory();
OfficeProgramm wordPresProgram = msShop.holeApp("Präsentation");
wordPresProgram.starten();//"Powerpoint startet"
}
}
In der Zusammenschau ergibt sich die finale Modellierung unseres ShopSystems. Sehr schön zeigt sich die Entsprechung der beiden Vererbungshierarchien von Program und SoftwareShop: Der abstrakte SoftwareShop kennt nur das ebenso abstrakte OfficeProgram. Die konkreten Subklassen kennen hingegen die konkreten Programme.
Zu den Vorteilen, die uns der Einsatz der "normalen" Factory brachte, kommen nun dank Factoy Method Pattern weitere:
Es zeigt sich, dass durch den Einsatz des Factory Method Patterns, ein hohes Maß an Flexibilität und Allgemeingültigkeit gewonnen wird, während zeitgleich die Wartung erleichtert wurde und Erweiterungen schnell und unkompliziert möglich sind.
Nach dieser Einführung wird im folgenden Abschnitt das Factory Method Design Pattern formalisiert, näher analysiert und diskutiert.
| Klasse | Factory Method Teilnehmer |
|---|---|
| abstrakter SoftwareShop | Creator |
| konkrete OfficeSuiteFactorys | ConcreteCreator |
| abstrakte OfficeProgram-Schnittstelle | Product |
| konkrete Programme | ConcreteProduct |
| Exkurs: Dependency Inversion Prinzip (frei nach [VKBF], Seite 137 ff.) | |
|---|---|
| Das Factory Method Pattern ermöglicht die Einhaltung des Dependency Inversion Prinzips, dem Prinzip der Abhängigkeitsumkehrung. Zugrunde liegt folgendes OO-Entwurfsprinzip: Stütze dich nie auf eine konkrete Klasse, sondern immer auf Abstraktion - und das beidseitig.Bei diesem Prinzip handelt es sich um eine konsequente Weiterentwicklung unseres "Programmiere immer auf eine Schnittstelle"-Entwurfsprinzips. Es gilt immer mit Abstraktionen zu arbeiten und das nicht von der hochschichtigen Komponenten zur tiefschichtigen Komponenten, sondern auch anders rum: von tiefschichtigen Komponenten zu hochschichtigen Komponenten. Denken wir an unseren SoftwareShop: |
|
|
![]() |
In unseren ursprünglichen Entwurf kümmerte sich der SoftwareShop selbst um die Instanziierung der konkreten OfficeProgramme. Er war damit abhängig von konkreten, tiefschichtigen Komponenten. Jede Änderung am OfficeProgrammportfolio würde eine Änderung des SoftwareShops - einer hohen Komponente - nach sich ziehen. |
Im finalen Entwurf kennt der SoftwareShop nur die Abstraktion OfficeProgramm und ist damit von den konkreten Klassen entkoppelt. Gleiches gilt für die konkreten Programme: Sie haben zwar an Abhängigkeit gewonnen, sind aber nur von der Abstraktion OfficeProgramm abhängig, da sie diese Klasse erweitern. Und genau an diesem Punkt hat sich die Abhängigkeit umgekehrt. Alle Komponenten - sowohl die der hohen Schicht (SoftwareShop), als auch die der tiefen (Konkrete Programme) - stützen sich gleichermaßen auf eine Abstraktion. Dank dieser Schnittstelle ist die hohe Komponente unabhängig von den tiefen Komponenten und umgekehrt. Das ist das Dependency-Inversion-Prinzip. |
Factory Method:
"Definiere eine Klassenschnittstelle mit Operationen zum Erzeugen eines Objekts, aber lasse Unterklassen entscheiden, von welcher Klasse das zu erzeugende Objekt ist. Fabrikmethoden ermöglichen es einer Klasse, die Erzeugung von Objekten an Unterklassen zu delegieren."
([GoF], Seite 131)

Das Factory Method Entwurfsmuster dient der Entkopplung des Clients von der konkreten Instanziierung einer Klasse. Das erstellte Objekt kann elegant ausgetauscht werden. Oft wird es zur Trennung von (zentraler) Objektverarbeitung und (individueller) Objektherstellung verwendet.
Der Erstellungscode eines Objektes (Product genannt) wird in einer eigenen Klasse (Creator, Factory) ausgelagert. Dieser Creator ist abstrakt und delegiert die konkrete Objektinstanziierung wiederrum an seiner Unterklasse. Erst die Unterklasse entscheidet welches Product erstellt wird. Da der Client sich komplett auf Abstraktion stützt (sowohl beim Creator als auch bei den Products), ist er vollkommen von den Implementierungen entkoppelt und unabhängig.
Der Creator ist abstrakt, und kennt nur die abstrakte Schnittstelle vom Product und instanziiert nicht ein konkretes Productobjekt, sondern lässt seine Unterklassen entscheiden, welches konkrete Product erzeugt werden soll. Dazu definiert es eine abstrakte Methode (die namensgebende factoryMethod()), die es in seiner createProduct()-Methode aufruft und die seine Unterklassen implementieren müssen. Unterklassen (ConcreateCreators) implementieren diese Methode und geben ein ConcreteProduct zurück.
Nachdem die Unterklasse des Creators das konkrete Product an den Creator zurückgegeben hat, kann der Creator noch allgemeinen Herstellungscode enthalten, die auf jedes Product angewandt werden muss, bevor es an den Client geliefert wird.
Der Creator kann beliebig erweitert werden (ConcreteCreatorB, ConcreateCreatorC) und somit verschiedene Products liefern.

Abstrakter Creator und konkreter Creator A und B:
public abstract class Creator {
public Product createProduct() {
//holt konkretes Product, weiß nicht welches.
Product product = factoryMethod();
//allgemeiner Productherstellungscode
product.setState(23);
product.prepare();
return product;
}
protected abstract Product factoryMethod();
}
class ConcreteCreatorA extends Creator {
protected Product factoryMethod() {
ConcreteProductA concProd = new ConcreteProductA();
//ggf. noch das ConcreateProductA bearbeiten...
return concProd;
}
}
class ConcreteCreatorB extends Creator {
protected Product factoryMethod() {
ConcreteProductB concProd = new ConcreteProductB();
//ggf. noch das ConcreateProductB bearbeiten...
return concProd;
}
}
Abstraktes Product und konkrete Products A und B:
public abstract class Product {
private int basisState;
public void prepare() {
System.out.println("preparing general Product");
}
public void setState(int pState) {
basisState = pState;
}
public int getState() {
return basisState;
}
public abstract int getPrice();
//further code
}
class ConcreteProductA extends Product {
@Override
public int getPrice() {
return 1400;
}
//further code
}
class ConcreteProductB extends Product {
@Override
public int getPrice() {
return 2200;
}
//further code
}
class ConcreteProductC extends Product {
@Override
public int getPrice() {
return 800;
}
//further code
}
Beispielclient:
public class Client {
public static void main(String[] args) {
Creator creator = new ConcreteCreatorA();
Product product = creator.createProduct();
System.out.println(product.getPrice());
}
}
Statt den Creator und seine Fabrikmethode abstrakt zu definieren, und somit die Unterklassenbildung zu erzwingen, kann der Creator konkret sein und eine Defaultimplementierung für die Fabrikmethode enthalten. Unterklassen können diese Fabrikmethode überschreiben. Dadurch wird die gesamte Unterklassenbildung optional und die Fabrikmethode wird nur aus Flexibilitätsgründen genutzt, um die Objekterstellung in einer separaten Methode zu kapseln, die dann von Unterklassen ggf. überschrieben werden kann.
Die Fabrikmethode bzw. die nach außen sichtbare Methode zur Herstellung eines Products (createProduct()) kann parametrisiert sein. Dadurch können dem Creator Informationen mitgegeben werden, welches konkrete Product er erstellen soll:
Anhand des Parameters wird entschieden, welches Produkt instanziiert werden soll:
public Werkzeug createWerkzeug(String pWerkzeug){
if (pWerkzeug.equals("Zange")){
return new Zange();
} else if (pWerkzeug.equals("Knochen")){
return new Knochen();
} else if (pWerkzeug.equals("Schlüssel")){
return new Schluessel();
} else {
return null;
}
}
Der Vorteil hierbei ist, dass schnell und unkompliziert neue Produkte hinzugefügt und ausgetauscht werden können.
Die Alternative ist eine Fabrikmethode ohne Parameter, sodass jeder ConcreteCreator genau ein Produkt herstellen kann. Für weitere Produkte müssen neue Unterklassen von Creator gebildet werden. Dies kann sinnvoll sein, wenn eine geringe Anzahl von Produkten hergestellt werden soll, der Erstellungsprozess allerdings umfangreicher und produktspezifischer ist.
Ein ConcreteCreator erzeugt genau ein Product:
abstract class Autowerkstatt {
public Auto getMercedes(){
Auto auto = createMercedes();
auto.testFahren();
auto.waschen();
auto.ausliefern();
return auto;
}
protected abstract Auto createMercedes();
}
class MercedesAutowerkstatt extends Autowerkstatt{
@Override
protected Auto createMercedes() {
Mercedes mercedes = new Mercedes();
//produktspezifische Erstellung
mercedes.setMercedesStern();
mercedes.superLackieren();
return mercedes;
}
}
Der Vergleich von Factory Method mit dem Abstract Factory Pattern wird im Artikel zur Abstract Factory durchgeführt.
Kommentare
Seite: 1 -