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.3 Abstract Factory

33.3 Abstract Factory

Das Entwurfsmuster der abstrakten Fabriken kapselt wieder die Implementierung und Erzeugung von Produkten, aber im Gegensatz zu den Fabrikmethoden, steht dieses mal nicht das Produkt als Einzelnes im Vordergrund, sondern eine Produktfamilie. Mit Familie ist ein ganzes Sortiment an Produkten gemeint. Die Herangehensweise ist entsprechend auch eine andere und zudem wesentlich komplexer.

Das Grundprinzip und somit auch das Einsatzfeld, ist folgendes. Man hat eine Anzahl von unterschiedlichen Produkten, welche nichts miteinander zu tun haben (also keine gemeinsame Basisklasse besitzen). Für die Erzeugung schafft man sich wieder eine Fabrik. Jetzt möchte man genau diese Produkte auf eine andere Art und Weise erzeugen. Hierfür baut man sich eine zweite Fabrik. Beide Fabriken erzeugen also ähnliche Produkte und somit schafft man ein Interface für die Fabriken.

Vereinfachte Struktur des Abstract Factory Pattern

Ein konkretes Beispiel wären die Firmen Siemens und AEG. Jene stellen Waschmaschinen und Geschirrspüler her. Abgesehen davon, dass diese beiden Produkte Küchengeräte sind, haben Sie nicht viel gemeinsam. Wir gehen also mal davon aus, dass sie von keiner gemeinsamen Klasse erben. Waschmaschine und Geschirrspüler sind jetzt wieder Abstrakte Produkte. Eine Fabrik, die Waschmaschinen und Geschirrspüler produzieren kann, ist eine abstrakte Fabrik. Das Werk von Siemens und das Werk von AEG sind jetzt konkrete Fabriken und jede Firma stellt jetzt seine eigene Variante seiner Geräte her. Die Grundfunktionalität bleibt erhalten, nämlich Wäsche waschen bzw. Geschirr spülen. Dennoch entscheidet jedes konkrete Produkt selber, wie viel Wasser und Strom benötigt wird.

Sie sehen also, dass es sich dieses mal um eine komplexere Struktur handelt. Dieses Pattern wird häufig für Frameworks benutzt, wobei man flexibel bei der Implementierung der Produkte sein möchte. Man kann also im Nachhinein nicht nur die Produkte ändern oder Austauschen, sondern auch den Prozess ihrer Erzeugung und Verwaltung. In dem von mir gewählten Beispiel wird es darum gehen, Autos und Motorräder zu erzeugen, wobei sie einmal optimiert sein sollen für OpenGL und einmal für DirectX. Es wird also eine abstrakte Fabrik geben, welche ein Interface darstellt und sagt, dass eine Fabrik Autos und Motorräder erzeugen kann. Davon abgeleitet werden dann konkrete Fabriken, welche sich um OpenGL Models und DirectX Models kümmern. Da diese Struktur etwas umfangreicher ist, hier zunächst ein Überblick aller Dateien, wobei die gestrichelten Linien zeigen sollen, wer was inkludiert.

Überblick der Dateistruktur des Abstract Factory Beispieles

Sie sehen, dass der Client jetzt nur beide Fabriken inkludiert und somit auch die abstrakte Fabrik und die abstrakten Produkte kennt. Alle konkreten Produkte und alle konkreten Fabriken können also im Nachhinein modifiziert werden, ohne dass der Client und alle nicht modifizierten Produkte und Fabriken, neu kompiliert werden müssen. Einzige Bedingung ist wieder, dass ihre Schnittstelle nicht modifiziert wird.

Zu Beginn definiere ich mir die abstrakten Klassen, also die Schnittstellen.

Auto.h:

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

Motorrad.h:

 1
 2
 3
 4
 5
					
// Schnittstelle für ein Motorrad
class IMotorrad {
	public:
		virtual void MotorradAusgabe() = 0;
};
					

AbstractFactory.h:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
					
#include "Auto.h"
#include "Motorrad.h"

// Schnittstelöle für eine Fabrik
class IAbstractFactory {
	public:
		virtual IAuto*		CreateAutoInstance()			= 0;
		virtual IMotorrad*	CreateMotorradInstance()		= 0;

		virtual void		ReleaseAutoInstance(IAuto*&)		= 0;
		virtual void		ReleaseMotorradInstance(IMotorrad*&)	= 0;
};
					

Sie sehen anhand der Schnittstelle für Auto und Motorrad, dass beide nicht viel können, aber das was sie können, unterscheidet sich. Interessanter ist jetzt das Interface der Fabrik. Hier sollten gleich zwei Sachen ins Auge fallen. Zum einen habe ich jetzt für jedes Produkt ein Methodenpaar zum Erzeugen und Freigeben. Der Standard sieht also vor, dass man hier keinen Parameter übergibt, welcher regelt, welches Produkt erzeugt werden soll. Sicher hätte man dies auch so machen können, aber da dies die Sache nicht einfacher macht und zudem vom Standard abweicht, habe ich darauf verzichtet.

Als zweites sollte auffallen, dass die Methoden nicht mehr statisch sind. Zum Erzeugen der Produkte benötigt man also später eine Instanz der konkreten Fabrik. In anderen Sprachen, wie z.B. Java, kann man diese Methoden durchaus statisch definieren, aber C++ verweigert dies. Da man also in C++ eine Instanz benötigt, ist es oft üblich, diese mit Hilfe des Singleton Pattern zu Implementieren. Da dies die Sache auch nicht einfacher macht, habe ich ebenfalls auf diesen Mechanismus verzichtet.

Schauen Sie sich als nächstes die OpenGL Produkte an.

MercedesGL.h:

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

// Mercedesklasse auf Basis von OpenGL
class CMercedesGL : public IAuto {
	public:
		virtual void AutoAusgabe();
};
					

MercedesGL.cpp:

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



// Spezielle Ausgabe auf der Konsole //////////////////////////////////////////
void CMercedesGL::AutoAusgabe(void) {
	printf("Mercedes mit OpenGL!\n");
} // AutoAusgabe //////////////////////////////////////////////////////////////
					

DukatiGL.h:

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

// Dukatiklasse auf Basis von OpenGL
class CDukatiGL : public IMotorrad {
	public:
		virtual void MotorradAusgabe();
};
					

DukatiGL.cpp:

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



// Spezielle Ausgabe auf der Konsole //////////////////////////////////////////
void CDukatiGL::MotorradAusgabe(void) {
	printf("Dukati mit OpenGL!\n");
} // MotorradAusgabe //////////////////////////////////////////////////////////
					

Wie Sie sehen, gibt es auch hier nicht viel Magie. Die Definition und Implementierung ist sehr schlicht gehalten. Ich implementiere lediglich die abstrakte Methode und lasse auch da nur eine Zeile auf der Konsole ausgeben.

Genau das gleiche Prinzip wende ich in der DirectX Familie an. Es wird sich also lediglich die Ausgabe ändern.

MercedesDX.h:

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

// Mercedesklasse auf Basis von DirectX
class CMercedesDX : public IAuto {
	public:
		virtual void AutoAusgabe();
};
					

MercedesDX.cpp:

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



// Spezielle Ausgabe auf der Konsole //////////////////////////////////////////
void CMercedesDX::AutoAusgabe(void) {
	printf("Mercedes mit DirectX!\n");
} // AutoAusgabe //////////////////////////////////////////////////////////////
					

DukatiDX.h:

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

// Dukatiklasse auf Basis von DirectX
class CDukatiDX : public IMotorrad {
	public:
		virtual void MotorradAusgabe();
};
					

DukatiDX.cpp:

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



// Spezielle Ausgabe auf der Konsole //////////////////////////////////////////
void CDukatiDX::MotorradAusgabe(void) {
	printf("Dukati mit DirectX!\n");
} // MotorradAusgabe //////////////////////////////////////////////////////////
					

Etwas interessanter wird jetzt die "main".

 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
					
#include "GLFabrik.h"
#include "DXFabrik.h"



// Hauptfunktion der Anwendung ////////////////////////////////////////////////
int main (int argc, char** argv) {
	CGLFabrik	oGLFabrik;
	CDXFabrik	oDXFabrik;

	IAuto*		pMercedesGL	= oGLFabrik.CreateAutoInstance();
	IMotorrad*	pDukatiGL	= oGLFabrik.CreateMotorradInstance();

	IAuto*		pMercedesDX	= oDXFabrik.CreateAutoInstance();
	IMotorrad*	pDukatiDX	= oDXFabrik.CreateMotorradInstance();

	pMercedesGL->AutoAusgabe();
	pDukatiGL->MotorradAusgabe();

	pMercedesDX->AutoAusgabe();
	pDukatiDX->MotorradAusgabe();

	oGLFabrik.ReleaseAutoInstance(pMercedesGL);
	oGLFabrik.ReleaseMotorradInstance(pDukatiGL);

	oDXFabrik.ReleaseAutoInstance(pMercedesDX);
	oDXFabrik.ReleaseMotorradInstance(pDukatiDX);

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

Ausgabe:

Mercedes mit OpenGL!
Dukati mit OpenGL!
Mercedes mit DirectX!
Dukati mit DirectX!
		

Unterm Strich befindet sich aber auch hier nichts spannendes. Wie Sie bereits wissen, findet man keinen Code mehr zum Erzeugen oder Freigeben, da dies wieder die Fabriken übernehmen. Des weiteren sei noch mal darauf hingewiesen, dass ich mir je eine Instanz pro Fabrik erzeuge.

Sie sehen also, dass die Implementierung dieses Entwurfsmustern gar nicht mal so schwer ist. Jedoch ist es aufwändig, da man viele Klassen und somit Dateien benötigt. Leider darf man hier keine Inlinemethoden benutzen, da man sonst nicht mehr ganz so flexibel wäre. Die ganze Sache hat aber noch einen weiteren großen Haken. Wie Sie vielleicht schon mitbekommen haben, ist es zwar wieder einfach möglich, Produkte zu modifizieren, aber das Hinzufügen neuer Produkte gestaltet sich als sehr aufwändig, da man alle Fabriken überarbeiten muss.

Abschließend sei noch erwähnt, dass es oft üblich ist, dass man das Fabrikmethodenmuster und die abstrakten Fabriken, gemeinsam nutzt. So werden oft Fabriken gebaut, die andere konkrete Fabriken erzeugen, welche dann erst ein konkretes Produkt erzeugen. In dem Sinne ist eine konkrete Fabrik also auch eine Art Produkt und die abstrakte Fabrik, also die Schnittstelle, wäre dann ein abstraktes Produkt. Sie sehen, dieses Spiel kann man sehr weit treiben und wenn man dann zusätzlich alles noch als Singleton macht, ist man an dem Punkt, an welchen man es auch übertreiben kann. Verwenden Sie also nicht auf Teufel komm raus Entwurfsmuster, sondern suchen Sie sich nur das günstigste heraus.

Zum Seitenanfang
Zum Inhaltsverzeichnis

© Copyright by Thomas Weiß, 2009 - 2012