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...

34.4 Decorator

34.4 Decorator

Das Entwurfsmuster Decorator, ist eine weitere Möglichkeit, Komponenten um Fähigkeiten zu erweitern. Wieder versucht man eine große Vererbungshierarchie zu entschärfen, indem man versucht eine Komponente und seine Fähigkeiten zu trennen. Man muss sich das in etwa wie ein Plugin vorstellen.

Vereinfachte Struktur des Decorator Pattern

Auf den ersten Blick sieht diese Geschichte wie das Composite Pattern aus, aber die Denkweise ist genau die entgegengesetzte. Während man im Kompositum mehr auf interne Fähigkeiten eingeht, beschäftigen sich die Dekorationen mit äußeren Fähigkeiten. Zum Beispiel kann ein Fenster verschiedene Komponenten beinhalten. Es zeichnet sich also selbst und ruft anschließend die Zeichenfunktionen der enthaltenen Kinder auf. Das Fenster steht also an oberster Stelle. Wie gesagt, funktionieren die Dekorationen genau umgekehrt. So könnte ein Fenster mit verschiedenen Rahmen dekoriert werden. In diesem Fall wird also erst der Dekorator anfangen den Rahmen zu zeichnen und dann die beinhaltete Komponente zeichnen lassen. In diesem Fall steht das Fenster also ganz unten bzw. am Ende der Verarbeitungskette.

Hier mal ein Beispiel, um dies besser zu verdeutlichen. Stellen Sie sich folgende ungünstige Vererbungshierarchie vor.

Beispiel für eine verschachtelte Vererbungshierarchie

Abgesehen davon, dass Mehrfachvererbung sehr Ungünstig ist, schränkt einen diese Struktur ein und macht es schwer, neue Fähigkeiten hinzuzufügen. Wenn man jetzt das Decorator Pattern zur Anwendung bringt, würde die Resultierende Vererbungshierarchie wie folgt aussehen.

Nach dem Decorator Pattern umstrukturiertes Beispiel

Wie Sie sehen, ist dies schon wesentlich übersichtlicher und zudem einfacher erweiterbar. Ein weiterer günstiger Nebeneffekt dieser Struktur ist, dass man die Dekorationen, also die Fähigkeiten, zur Laufzeit austauschen kann, was die vorhergehende Struktur nicht erlaubt. Aber wie bekommt man jetzt wieder ein Fenster hin, welches modal ist, Fadeeffekte besitzt und zudem ein Gradient im Hintergrund aufweist? Schauen Sie sich dazu folgende Grafik an

Mögliche Implementierung des Beispieles

Wie Sie sehen, können Dekorationen wiederum Dekorationen aufnehmen (und so weiter), bis letztlich die eigentliche Komponente folgt. Um dies noch mehr zu verdeutlichen, zeige ich Ihnen eine mögliche Implementierung dieses Beispieles, wobei ich es leicht abwandeln werde, um zu zeigen, dass man die Komponenten tatsächlich zur Laufzeit austauschen kann. Ich beginne mit der Definition der Schnittstelle.

WindowInterface.h:

 1
 2
 3
 4
 5
 6
 7
 8
					
#include <stdlib.h>
#include <stdio.h>

// Schnittstelle für alle Komponenten
class IWindow {
	public:
		virtual void Draw(void) = 0;
};
					

Wie sie sehen können, wird mein Fenster nicht viel können, außer sich auszugeben. Ich habe wieder bewusst auf alle Spielereien verzichtet, damit der Fokus auf dem Entwurfsmuster erhalten bleibt. Es folgt nun die Definition und Implementierung einer konkreten Fensterklasse.

WindowImplementation.h

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

// Klasse zur Darstellung eines Fensters
class CWindowImplementation : public IWindow {
	public:
		CWindowImplementation(int& iValue) : m_iValue(iValue) {}

		virtual void Draw(void);

	private:
		int m_iValue;
};
					

WindowImplementation.cpp:

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



// Fenster Zeichnen ///////////////////////////////////////////////////////////
void CWindowImplementation::Draw(void) {
	printf("CWindowImplementation(%i)", m_iValue);
} // Draw /////////////////////////////////////////////////////////////////////
					

Diese Klasse besitzt lediglich ein Attribut, welches in der Zeichenfunktion mit dem Klassennamen ausgegeben wird. Der Fokus liegt hier auf der Ausgabe. Das Attribut wird bewusst innerhalb der Klammern ausgegeben, um zu verdeutlichen, dass interne Member auch intern gezeichnet werden.

Es folgt nun die Definition der Dekorationen und ihrer Schnittstelle. Aus Gründen der Vereinfachung, habe ich sie alle in einer Header-Datei zusammengefasst. Für eine tatsächliche Realisierung rate ich aber davon ab.

Decorator.h:

 1
 2
 3
 4
 5
 6
 7
 8
19
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
					
#include "WindowImplementation.h"

// Basisklasse für eine Dekoration
class CDecorator : public IWindow {
	public:
		CDecorator(IWindow* pComponent) : m_pComponent(pComponent) {}

		virtual void Draw(void) = 0;

	protected:
		IWindow* m_pComponent;
};



// Klasse zur Gradientendekoration einer Komponente
class CGradientEffect : public CDecorator {
	public:
		CGradientEffect(IWindow* pComponent) : CDecorator(pComponent) {}

		virtual void Draw(void);
};


// Klasse zur Fadedekoration einer Komponente
class CFadeEffect : public CDecorator {
	public:
		CFadeEffect(IWindow* pComponent) : CDecorator(pComponent) {}

		virtual void Draw(void);
};



// Klasse zur Modaldekoration einer Komponente
class CModalEffect : public CDecorator {
	public:
		CModalEffect(IWindow* pComponent) : CDecorator(pComponent) {}

		virtual void Draw(void);
};
					

Decorator.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
34
35
36
37
					
#include "Decorator.h"



// Dekoration und Komponente Zeichnen /////////////////////////////////////////
void CGradientEffect::Draw(void) {
	// Wenn es eine Komponente gibt
	if (m_pComponent != NULL) {
		printf("CGradientEffect(");
		m_pComponent->Draw();
		printf(")");
	} // end of if
} // Draw /////////////////////////////////////////////////////////////////////



// Dekoration und Komponente Zeichnen /////////////////////////////////////////
void CFadeEffect::Draw(void) {
	// Wenn es eine Komponente gibt
	if (m_pComponent != NULL) {
		printf("CFadeEffect(");
		m_pComponent->Draw();
		printf(")");
	} // end of if
} // Draw /////////////////////////////////////////////////////////////////////



// Dekoration und Komponente Zeichnen /////////////////////////////////////////
void CModalEffect::Draw(void) {
	// Wenn es eine Komponente gibt
	if (m_pComponent != NULL) {
		printf("CModalEffect(");
		m_pComponent->Draw();
		printf(")");
	} // end of if
} // Draw /////////////////////////////////////////////////////////////////////
					

Wie Sie anhand der Zeichenfunktionen sehen können, wird erst die Dekoration selbst gezeichnet und anschließend erfolgt der Aufruf der Zeichenfunktion der Komponente. Das hier nach diesem Aufruf noch etwas gezeichnet wird, habe ich in diesem Fall nur aus ästhetischen Gründen gemacht.

Abschließend noch die Implementierung in der "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
					
#include "Decorator.h"
#include "WindowImplementation.h"



// Hauptfunktion der Anwendung ////////////////////////////////////////////////
int main(int argc, char** argv) {
	int			iValue		= 1;
	CWindowImplementation*	pWindow		= new CWindowImplementation(iValue);
	CGradientEffect*	pGradient	= new CGradientEffect(pWindow);
	CFadeEffect*		pFade		= new CFadeEffect(pGradient);

	pFade->Draw();
	printf("\n");

	delete pFade;

	CModalEffect*		pModal		= new CModalEffect(pGradient);

	pModal->Draw();
	printf("\n");

	delete pWindow;
	delete pGradient;
	delete pModal;

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

Ausgabe:

CFadeEffect(CGradientEffect(CWindowImplementation(1)))
CModalEffect(CGradientEffect(CWindowImplementation(1)))
		

Wie die Ausgabe zeigt, ist es also in der Tat möglich, die Dekorationen während der Laufzeit umzuhängen. Momentan ist das Austauschen der Dekorationen noch etwas unergonomisch, aber wenn man noch zusätzliche Getter und Setter implementiert, kann man auch ohne weiteres die Dekorationen in der Mitte austauschen. Des weiteren sieht man sehr schön, dass sich die Dekorationen um die eigentliche Komponente, also das Fenster, hüllen.

Abschließend möchte ich aber noch auf ein großen Nachteil eingehen. Da die Decorator Eigenschaften der Wrapper aufweisen, welche auch u.a. die Aufrufe weiterleiten, nachdem sie ihre eigene Logik abgearbeitet haben, müssen sie auch die komplette Schnittstelle implementieren. Eine Erweiterung der Komponenten hat also zur Folge, dass auch alle Dekorationen erweitert werden müssen, obwohl die Neuerung vielleicht gar nichts mit der jeweiligen Dekoration zu tun hat.

Zum Seitenanfang
Zum Inhaltsverzeichnis

© Copyright by Thomas Weiß, 2009 - 2012