Twitter und Facebook-Anbindung
X
Tweet Follow @twitterapi
!!! Anbindung an twitter und facebook öffnen !!!

Wenn Ihnen mein Online-Buch gefällt,
dann bedanken Sie sich doch mit einer kleinen Spende...

33.2 Factory Method

33.2 Factory Method

Dieses Erzeugungsmuster dient zur Kapselung der Erzeugung und Implementierung von Produkten. Man schafft eine zentrale Stelle, an der Produkte erzeugt werden und wenn später neue Produkte hinzukommen, braucht man nur noch eine Stelle im Quelltext ändern und nicht unzählige. Dies reduziert Fehlerquellen.

Die zentrale Stelle wird durch eine Fabrik repräsentiert, welche nichts anderes macht, als ein gewünschtes Produkt zu erzeugen bzw. freizugeben. Wie der Produktwunsch der Fabrik mitgeteilt wird, bleibt dem Programmierer überlassen. Man könnte also ein String mit einem Klassennamen übergeben oder sich in der Fabrik einen Aufzählungstyp definieren.

Vereinfachte Struktur eines Factory Musters

Wie Sie sehen können, inkludiert der Client nur noch die Fabrik und kennt dadurch nur eine abstrakte Definition eines Produktes. Solange alle Produkte sich an diese Schnittstelle halten, braucht der Client sie nicht zu kennen, um sie zu benutzen. Dies macht vor allem dann Sinn, wenn man den Quelltext der Produkte nicht ausliefern möchte.

Ein typisches Anwendungsbeispiel wäre z.B. ein Computerspiel, welches mehrere Autos benutzen soll. Das Auto stellt eine abstrakte Schnittstelle dar, welche definiert, was ein Auto alles zu können haben muss. Ein Mercedes oder ein Opel wären dann konkrete Autos, welche durch die Fabrik erzeugt werden. Die Spiellogik selbst braucht nicht zu wissen, wie man ein Mercedes oder Opel erzeugen muss. Ihr ist es nur wichtig, dass sie die rudimentären Methoden aufrufen kann. Ein netter Nebeneffekt ist, dass die Implementierung eines konkreten Autos im Nachhinein aktualisiert werden kann, ohne das komplette Spiel neu kompilieren zu müssen. Einzige Bedingung ist, dass die Schnittstelle unberührt bleibt.

Im folgenden Quelltexten habe ich mich genau an dieses Beispiel mit den Autos gehalten und zeige Ihnen, wie man das Factory Methoden Pattern implementiert. Dabei habe ich wieder auf das ganze Drumherum verzichtet, damit der Fokus auf das Entwurfsmuster erhalten bleibt. Schauen Sie sich zunächst die Schnittstelle eines Autos an.

Auto.h:

 1
 2
 3
 4
 5
 6
					
// Schnittstelle für ein Auto
class IAuto {
	public:
		virtual void Anfahren()	= 0;
		virtual void Bremsen()	= 0;
};
					

Wie Sie sehen, wird ein Auto in meinem Beispiel nicht viel können, aber mit der abstrakten Definition der Methoden, erzwinge ich eine Implementierung dieser Methoden in den erbenden Klassen. Auffällig sollte hier der Klassenname sein. Ich leite ihn bewusst mit einem "I" und nicht mit "C" ein, um noch mehr herauszustellen, dass es sich um eine Schnittstelle (Interface), also um eine "pure abstract" Klasse handelt.

Es folgt die Definition der zwei konkreten Autos, welche dieses Interface implementieren.

Mercedes.h:

 1
 2
 3
 4
 5
 6
 7
 8
					
#include "Auto.h"

// Klasse für die Modelierung eines Mercedes
class CMercedes : public IAuto {
	public:
		virtual void Anfahren();
		virtual void Bremsen();
};
					

Opel.h:

 1
 2
 3
 4
 5
 6
 7
 8
					
#include "Auto.h"

// Klasse für die Modelierung eines Opels
class COpel : public IAuto {
	public:
		virtual void Anfahren();
		virtual void Bremsen();
};
					

Wie zu erwarten, passiert hier nichts spannendes und auch die folgende Implementierung wird nicht sehr viel aufregender.

Mercedes.cpp:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
					
#include <stdio.h>
#include "Mercedes.h"



// Implementierung der Beschleunigungsfunktion ////////////////////////////////
void CMercedes::Anfahren(void) {
	printf("Der Mercedes gibt Gas!\n");
} // Anfahren /////////////////////////////////////////////////////////////////



// Implementierung der Bremsfunktion //////////////////////////////////////////
void CMercedes::Bremsen(void) {
	printf("Der Mercesdes bremst\n");
} // Bremsen //////////////////////////////////////////////////////////////////
					

Opel.cpp:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
					
#include <stdio.h>
#include "Opel.h"



// Implementierung der Beschleunigungsfunktion ////////////////////////////////
void COpel::Anfahren(void) {
	printf("Der Opel gibt Gas!\n");
} // Anfahren /////////////////////////////////////////////////////////////////



// Implementierung der Bremsfunktion //////////////////////////////////////////
void COpel::Bremsen(void) {
	printf("Der Opel bremst\n");
} // Bremsen //////////////////////////////////////////////////////////////////
					

Wie Sie sehen, passiert tatsächlich nicht viel. Die zwei Methoden werden lediglich etwas auf der Konsole ausgeben, wenn sie aufgerufen werden. So kann ich Ihnen später zeigen, dass die Fabriken tatsächlich das tun, was sie sollen und weil wir dabei sind, hier die Definition und die Implementierung der Fabrik.

AutoFabrik.h:

 1
 2
 3
 4
 5
 6
 7
 8
 9
					
#include "Auto.h"

// Klasse zur Erzeugung eines Autos
class CAutoFabrik {
	public:
		enum EAuto {eaMercedes, eaOpel, eaCount, eaNone};
		static IAuto* CreateInstance(const EAuto& eAuto);
		static void ReleaseInstance(const EAuto& eAuto, IAuto*& pAuto);
};
					

AutoFabrik.cpp:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
					
#include "AutoFabrik.h"
#include "Mercedes.h"
#include "Opel.h"



// Erzeugt ein spezielles Auto ////////////////////////////////////////////////
IAuto* CAutoFabrik::CreateInstance(const EAuto& eAuto) {
	IAuto* pResult	= NULL;

	// Je nachdem, welches Auto erzeugt werden soll
	switch (eAuto) {
		case eaMercedes:	pResult = new Cmercedes();	break;
		case eaOpel:		pResult = new Copel();		break;
		// ...
	} // end of switch

	return pResult;
} // CreateInstance ///////////////////////////////////////////////////////////



// Freigeben eines speziellen Fahrzeuges //////////////////////////////////////
void CAutoFabrik::ReleaseInstance(const EAuto& eAuto, IAuto*& pAuto) {
	// Je nachdem, welches Auto erzeugt werden soll
	switch (eAuto) {
		case eaMercedes:	delete static_cast<CMercedes*>(pAuto);	break;
		case eaOpel:		delete static_cast<COpel*>(pAuto);	break;
		// ...
	} // end of switch

	pAuto = NULL;
} // ReleaseInstance //////////////////////////////////////////////////////////
					

Wie Sie anhand der Header-Datei sehen können, habe ich mich für einen Aufzählungstyp entschieden, um der Fabrik mitzuteilen, welches Produkt ich haben möchte. In der Implementierung der statischen Klassenmethoden, entscheide ich dann mittels einer Switch Anweisung, welches konkrete Auto erzeugt und somit zurückgegeben bzw. gelöscht wird. Dass man hier statische Klassenmethoden nutzt, ist auch sehr Typisch. Man braucht nicht extra ein Objekt, wenn man die Klasse nur nutzen will, um ein Objekt zu Erzeugen bzw. Freizugeben.

Ein großer Pferdefuß in C++ ist, dass man die Fabrik benötigt, um das Objekt wieder Freizugeben. Würde der Client das Objekt löschen, würde nur die Hälfte des Speichers freigegeben werden. Man muss sich also im Client irgendwie merken, was sich hinter einem Zeiger verbirgt. Ein Array aus Fahrzeugen ist also nicht unbedingt empfehlenswert. In solch einem Fall benötigt man ein Array aus Strukturen, in welcher man sich den Zeiger und den Typ hält. Wenn man allerdings das Singelton Pattern benutzt und jedes Auto nur ein einziges mal benötigt, geht man anders an die Sache heran. In solch einem Fall erzeugt man sich tatsächlich eine Instanz der Fabrik und dann ist auch die Methode zum Holen des Zeigers nicht mehr statisch. Intern könnte die Fabrik dann im Konstruktor ein Array mit allen möglichen Autos erzeugen und im Destruktor wieder freigeben. Entsprechend würde man die Methode zum Holen auch umbenennen in "GetInstance". Jene gibt dann nur den jeweiligen Zeiger aus dem internen Array zurück. Zudem deutet der Methodenname auch wieder auf das Singelton Pattern hin.

Interessant ist hierbei auch, dass nicht die Header-Datei der Fabrik die Produkte, also die konkreten Autos, includiert, sondern die CPP-Datei. Dies ist deshalb wichtig, damit man die Produkte besser ändern kann. Beispielweise ist es so möglich, dass ein spezielles Auto eine Eigenschaft mehr bekommt, ohne das ganze Spiel neu kompilieren zu müssen.

Abschließend noch die "main", welche den Client darstellt.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
					
#include "AutoFabrik.h"



// Hauptfunktion der Anwendung ////////////////////////////////////////////////
int main (int argc, char** argv) {
	IAuto* pMercedes	= CAutoFabrik::CreateInstance(CAutoFabrik::eaMercedes);
	IAuto* pOpel		= CAutoFabrik::CreateInstance(CAutoFabrik::eaOpel);

	pMercedes->Anfahren();
	pOpel->Bremsen();

	CAutoFabrik::ReleaseInstance(CAutoFabrik::eaMercedes, pMercedes);
	CAutoFabrik::ReleaseInstance(CAutoFabrik::eaOpel, pOpel);

	return 0;
} // main /////////////////////////////////////////////////////////////////////
					

Ausgabe:

Der Mercedes gibt Gas!
Der Opel bremst!
		

Wie Sie anhand der Ausgabe sehen, arbeitet die Fabrik wie gewünscht und Sie finden in der "main" keinen Code mehr, welcher auf die speziellen Autos eingeht. Wichtig ist auch hier nochmal, dass das Erzeugen und Freigeben ausgelagert wurde und somit ebenfalls nicht mehr auftaucht.

Die ganze Sache hat aber einen großen Haken. Laut Definition ist die Schnittstelle der Produkte rein abstrakt, was zu Folge hat, dass man üblicherweise keine Eigenschaften definiert. Falls man dies macht, muss man sie mindestens "protected" definieren und auf Setter und Getter sollte man auch verzichten. Würde man sie implementieren, müsste man sie mindestens virtuell definieren, was wiederum sehr teuer ist, da wieder die Indirektionen über die virtuelle Tabelle, in den abgeleiteten Klassen hinzukommt.

Zum Seitenanfang
Zum Inhaltsverzeichnis

© Copyright by Thomas Weiß, 2009 - 2012