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

34.6 Proxy

Der Begriff Proxy sollte Ihnen aus dem Bereich der Netzwerktechnik bekannt vorkommen. Sie sind Stellvertreter für andere Dinge und stellen somit eine Art Adapter dar. Im Gegensatz zum Entwurfsmuster der Adapter, bilden Proxys die Schnittstelle eines Subjektes ab und erweitern sie ggf. um weitere Funktionalitäten.

Vereinfachte Struktur des Proxy Pattern

Wozu wird dieses Entwurfsmuster benötigt? Proxys werden in verschiedenen Bereichen eingesetzt und so unterscheidet man vier verschiedene Arten, je nach Anwendungsgebiet.

Die erste Art, die "Remoteproxys", kommen immer dann zum Einsatz, wenn man auf ein Objekt zugreifen will, welches lokal oder im Netzwerk liegt. Dabei soll es nach außen hin keinen sichtbaren Unterschied geben. Im Sektor der COM Programmierung (DCOM und ActiveX), gibt es immer solche Stellvertreterobjekte.

Dann gibt es s.g. "virtuelle Proxys". Sie kapseln meist größere Objekte und werden häufig in Verbindung mit den Fliegengewichten und Singleton eingesetzt. Die teuren Objekte werden meist nur einmalig angelegt und die möglichen Abweichungen werden auf die Proxys verlagert, welche dann für die eigentliche Arbeit, alle auf die gleiche Objektinstanz verweisen.

Die dritte Art der Proxys kommt immer dann zum Einsatz, wenn man verschiedene Zugriffsrechte auf ein Objekt kapseln möchte. Man nennt diese Stellvertreter "Schutzproxys". Ein einfaches Beispiel wäre der Zugriff auf ein Konto. Während das Konto nur Methoden zum ändern bereitstellt, wird eine Passwortabfrage im Proxy realisiert. Ein etwas komplexeres Beispiel könnte diverse Benutzerrechte kapseln. So bräuchte das eigentliche Objekt nicht ständig prüfen, welcher Benutzer gerade eine Anfrage stellt und ob er das nötige Recht besitzt, sondern der Benutzer bekommt einen ihm zugewiesenen Proxy, welcher dann nur eine Teilmenge der Funktionalitäten bereitstellt, also nicht das komplette Interface erbt oder implementiert (bei einer Vererbung müssen schon alle abstrakten Methoden implementiert werden, aber manche machen dann einfach nichts).

Letztlich gibt es da noch die s.g. "Smart References", welche den Zugriff und die Speicherverwaltung eines Objektes kapseln. Dabei wird jede Referenz auf das eigentliche Objekt mitgezählt und falls die letzte Referenz entfernt wurde, wird dafür gesorgt, dass das Objekt freigegeben wird (man baut sich somit einen Carbage Collector). Zudem ist es möglich, die eigentlichen Objekte mit Zusatzfunktionen auszustatten, was dem Dekorationsmuster gleich kommt. Das eigentliche Objekt und seine Schnittstelle bleiben davon unberührt.

Es gibt Situationen, in welchen es Sinnvoll ist, den Stellvertreter nicht von der Schnittstelle erben zu lassen. Im Falle der Schutzproxys ist dies beispielsweise Sinnvoll (da man sonst, wie erwähnt, Methoden bereitstellen müsste, welche keine Funktionalität besitzen).cIm folgenden Beispiel werde ich Ihnen zeigen, wie ein solcher Schutzproxy aussehen könnte. Es wird darum gehen, eine Klasse zu entwerfen, welche auf eine Datenbank zugreift. Zudem entwerfe ich zwei Proxys, welche unterschiedliche Zugriffsrechte realisieren (einer nur mit Leserecht und der andere mit vollem Zugriffsrecht). Um die Sache möglichst flexibel zu halten, wird es noch zwei Basisklassen geben, wobei eine, eine Allgemeine Datenbankverbindung beschreibt und die andere, einen allgemeinen Proxy. Schauen Sie sich zunächst das Klassendiagramm an.

Möglicher Aufbau einer gesicherten Datenbankverbindung

Wie Sie sehen, halte ich die Schnittstelle und somit die Funktionsweise, sehr einfach. Neben dem Verbinden bzw. Trennen der Verbindung, wird es nur eine Methode zum Lesen und eine zum Schreiben geben, wobei ich letztere absichtlich mit keinen Parametern ausgestattet habe, da es mir weniger auf das tatsächliche Lesen und Schreiben ankommt. Diese zwei Methoden werden später lediglich etwas auf der Konsole ausgeben.

Fangen wir mit der Definition der Schnittstelle für Datenbankverbindungsklassen an.

AbstractDBConnection.h:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
					
#include <stdlib.h>

// Schnittstelle für ein Datenbankobjekt
class CAbstractDBConnection {
	public:
		// Datenbankverbindung aufbauen
		virtual void Connect()		= 0;
		// Datenbankverbindung trennen
		virtual void Disconnect()	= 0;
		// Daten auslesen
		virtual void GetData() const	= 0;
		// Daten speichern
		virtual void SetDate() const	= 0;

	protected:
		static CAbstractDBConnection*	s_pConnection;
		static bool			s_bConnected;

		// Konstruktor
		CAbstractDBConnection();
};
					

Was an dieser Stelle besonders auffällig sein sollte ist, dass der Konstruktor geschützt ist. Damit verhindere ich, dass man sich später nach belieben eine Instanz der Klasse erzeugen kann, da sie als Singleton benutzt werden soll. Zu diesem Zweck habe ich auch eine statische Klassenvariable definiert. Abgeleitete Klassen müssen also noch die Methoden "GetInstance" und "Release" implementieren. Warum habe ich die Methode und die Klassenvariablen jetzt als geschützt und nicht als privat deklariert? Dies ist ganz einfach zu erklären. Wären sie privat, könnten abgeleitete Klassen nicht mehr auf den Basiskonstruktor zugreifen und die Methoden zum Erzeugen und Freigeben hätten keinen Zugriff mehr auf die Klassenvariablen. Auf private Methoden und Eigenschaften kann man in abgeleiteten Klassen nicht mehr zugreifen, jedoch auf öffentliche und geschützte Bereiche.

Als nächstes folgt die Implementierung dieser Klasse.

AbstractDBConnection.cpp:

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



CAbstractDBConnection*	CAbstractDBConnection::s_pConnection	= NULL;
bool			CAbstractDBConnection::s_bConnected	= false;

// Konstruktor
CAbstractDBConnection::CAbstractDBConnection() {}
					

Wie zu erwarten war, erfolgt hier lediglich die Initialisierung der Klassenvariablen und die formale Implementierung des Konstruktors. In meiner Implementierung hat der Konstruktor keine Aufgabe, was aber in einer tatsächlichen Implementierung nicht der Fall sein muss. Auch hier habe ich wieder eine Vereinfachung vorgenommen.

Als nächstes folgt eine konkrete Datenbankverbindung auf Basis von MySQL, wobei ich auch hier aus Gründen der Vereinfachung, auf spezifische Merkmale verzichtet habe und somit nur der Klassenname auf diese Datenbankart hinweist.

MySQLConnection.h:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
					
#include <stdio.h>
#include "AbstractDBConnection.h"

// Klasse zur Kommunikation mit einer MySQL Datenbank
class CMySQLConnection : public CAbstractDBConnection {
	public:
		// Ggf. Datenbankverbindung aufbauen
		virtual void Connect();
		// Ggf. Datenbankverbindung trennen
		virtual void Disconnect();
		// Daten aus der Datenbank holen
		virtual void GetData() const;
		// Daten in die Datenbank schreiben
		virtual void SetDate() const;

		// Ggf. Objektinstanz erzeugen
		static CMySQLConnection* GetInstance();
		// Ggf. Objektinstanz freigeben
		static void Release();

	private:
		// Konstruktor - Initialisieren
		CMySQLConnection();
};
					

Neben der Überladung der abstrakten Methoden, kommen hier noch die zwei Methoden zum Erzeugen und Freigeben der Instanz hinzu, wie Sie dies vom Singleten Pattern gewohnt sind. Auch der Konstruktor ist jetzt privat, was zur Folge hat, dass von dieser Klasse nicht mehr geerbt werden kann.

Es folgt nun die Implementierung dieser Klasse.

MySQLConnection.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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
					
#include "MySQLConnection.h"



// Konstruktor - Initialisieren ///////////////////////////////////////////////
CMySQLConnection::CMySQLConnection() : CAbstractDBConnection() {} /////////////



// Ggf. Datenbankverbindung aufbauen //////////////////////////////////////////
void CMySQLConnection::Connect() {
	// Nur wenn noch keine Verbindung besteht
	if (!s_bConnected) {
		s_bConnected = true;
		printf("CMySQLConnection::Connect\n");
	} // end of if
} // Connect //////////////////////////////////////////////////////////////////



// Ggf. Datenbankverbindung trennen ///////////////////////////////////////////
void CMySQLConnection::Disconnect() {
	// Nur wenn eine Verbindung besteht
	if (s_bConnected) {
		s_bConnected = false;
		printf("CMySQLConnection::Disconnect\n");
	} // end of if
} // Disconnect ///////////////////////////////////////////////////////////////



// Daten aus der Datenbank holen //////////////////////////////////////////////
void CMySQLConnection::GetData() const {
	// Nur wenn eine Verbindung besteht
	if (s_bConnected) printf("CMySQLConnection::GetData\n");
} // GetData //////////////////////////////////////////////////////////////////



// Daten in die Datenbank schreiben ///////////////////////////////////////////
void CMySQLConnection::SetDate() const {
	// Nur wenn eine Verbindung besteht
	if (s_bConnected) printf("CMySQLConnection::SetData\n");
} // SetDate //////////////////////////////////////////////////////////////////



// Ggf. Objektinstanz erzeugen ////////////////////////////////////////////////
CMySQLConnection* CMySQLConnection::GetInstance() {
	// Instanze erzeugen, wenn sie noch nicht existiert
	if (s_pConnection == NULL) s_pConnection = new CMySQLConnection();

	return static_cast<CMySQLConnection*>(s_pConnection);
} // GetInstance //////////////////////////////////////////////////////////////



// Ggf. Objektinstanz freigeben ///////////////////////////////////////////////
void CMySQLConnection::Release() {
	// Instanz freigeben, wenn es sie gibt
	if (s_pConnection != NULL) {
		// Wenn noch eine Datenbankverbindung besteht
		if (s_bConnected) s_pConnection->Disconnect();

		delete s_pConnection;
		s_pConnection = NULL;
	} // end of if
} // Release //////////////////////////////////////////////////////////////////
					

Wie bereits angekündigt, passiert hier nicht wirklich viel und somit ist die Implementierung mehr oder weniger formaler Natur. Einziges Augenmerk liegt hier auf den Methoden "GetInstance" und "Release". Nach dem Erzeugen muss man noch eine Typumwandlung vornehmen, da die statische Klassenvariable in der Basisklasse definiert wurde und man somit nur einen Zeiger auf die Schnittstelle und nicht auf die eigentliche Klassen besäße (Polymorphismus). Beim Freigeben muss zudem noch sichergestellt werden, dass die Verbindung ordnungsgemäß beendet wird, falls dies noch nicht erledigt wurde.

Im nächsten Teil wird es schon interessanter, denn jetzt folgen die Proxys, welche die eben definierte Datenbankverbindung umhüllt. Da hier nicht mehr viel Logik vorhanden ist (die Aufrufe werden nur weitergeleitet), habe ich mir erlaubt, die Methoden inline zu definieren. Zudem habe ich mich für ein Template entschieden, damit ich die Proxys auf alle Datenbankarten anwenden kann. Ohne den Templatemechanismus müsste ich sonst für jede Datenbankverbindung, eigene Stellvertreter entferfen.

Auch hier wird es wieder eine Basisklasse geben, um die Implementierungen der konkreten Proxys so schlank wie möglich zu halten. Zudem vermeide ich das Definieren abstrakter Methoden, damit die abgeleiteten Stellvertreter, zum Einen schlank bleiben und zum Anderen keine virtuellen Tabellen benutzen müssen, was sich positiv auf die Ausführungsgeschwindigkeit auswirken wird. Gerade dieser Aspekt sollte nicht außer acht gelassen werden, da man sich durch Proxys ohnehin zusätzliche Funktionsaufrufe einhandelt.

AbstracktDBConnectionProxy.h:

 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
					
#include <stdio.h>

// Basisklasse für einen Datenbankverbindungsstellvertreter
template<class TDBConnectionType>
class CAbstractDBConnectionProxy abstract {
	public:
		// Konstruktor - Initialisieren
		CAbstractDBConnectionProxy() : m_pConnection(NULL) {
			m_pConnection = TDBConnectionType::GetInstance();
		} // CabstractDBConnectionProxy

		// Destructor - Aufräumen
		~CAbstractDBConnectionProxy() {
			m_pConnection = NULL;
			TDBConnectionType::Release();
		} // ~CabstractDBConnectionProxy

		// Datenbankverbindung aufbauen
		void Conncect() { m_pConnection->Connect(); }

		// Datenbankverbindung trennen
		void Disconnect() { m_pConnection->Disconnect(); }

	protected:
		CAbstractDBConnection* m_pConnection;
};
					

Wie Sie sehen, kümmert sich die Basisklasse um grundlegende Aufgaben, wie das Verwalten der Verbindung, da dies für jede Datenbankart notwendig ist. Da sich aus Sicht des Proxys dieses Verhalten nie ändert (wie die Datenbankverbindung tatsächlich aufgebaut werden muss, regelt die Datenbankverbindungsklasse), können diese Methoden also ohne Bedenken in der Basisklasse platziert werden.

Da jetzt das grundlegende Verhalten Implementiert wurde, können jetzt die speziellen Schutzproxys entworfen werden. Da die Basisklasse wie erwähnt den Großteil schon kapseln, werden sie sehr klein und deshalb habe ich die zwei konkreten Stellvertreter, in einer Datei untergebracht.

DBConnectionProxys:

 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 "AbstractDBConnectionProxy.h"

// Klasse, welche nur lesenden Zugriff auf eine Datenbank gestattet
template<class TDBConnectionType>
class CReadOnlyDBConnection : public CAbstractDBConnectionProxy<TDBConnectionType> {
	public:
		// Lesender Zugriff
		void GetData() const {
			m_pConnection->GetData();
		} // GetDate
};



// Klasse, welche lesenden und schreibenden Zugriff auf eine Datenbank gestattet
template<class TDBConnectionType>
class CFullAccessDBConnection : public CAbstractDBConnectionProxy<TDBConnectionType> {
	public:
		// Lesender Zugriff
		void GetData() const {
			m_pConnection->GetData();
		} // GetDate

		// Lesender Zugriff
		void SetData() const {
			m_pConnection->SetDate();
		} // GetDate
};
					

Sie sehen also, dass es jetzt ohne Probleme möglich ist, Teile einer Schnittstelle zu implementieren, um so den Client in seiner Handlungsmöglichkeit einzuschränken.

Des weiteren sollte auffallen, dass zumindest die Methode "GetData" doppelt implementiert wurde. Hier könnte man noch auf die Idee kommen, die Klasse mit dem Vollzugriff von der eingeschränkten Klasse erben zu lassen um diese Dopplung zu Vermeiden. Von diesem Vorgehen rate ich allerdings aus mehreren Gründen ab. Zum Einen sollte man nur Vererben, wenn sich im realen Leben Methaphern für die einzelnen Klassen bilden lassen. Basisklassen zu schaffen, nur weil etwas doppelt erscheint, kann auf Kurz oder Lang, nach hinten losgehen, was mich zum zweiten Grund bringt. Auch das Lesen könnte unterschiedlich implementiert werden. Beispielsweise kann der eine Proxy auf Tabelle A und B zugreifen und ein anderer auf Tabelle A und C. Von welcher Klasse soll man jetzt erben? Klüger wäre es also, sich für jede Tabelle einen Proxy zu bauen und jene dann in einer zentralen Instanz zu aggregieren. Andernfalls kann sich die Vererbungshierarchie sehr schnell aufblähen und unübersichtlich werden. Zum Anderen sollten die Proxys, wie erwähnt, nicht viel Logik implementieren, was zur Folge hat, dass Aufrufe in den meisten Fällen nur weiter delegiert werden. Somit h¨lt sich eine mehrfache Implementierung einer Methode in Grenzen. Zudem ist die Wahrscheinlichkeit, dass sich an diesem Verhalten später etwas ändern wird, sehr gering (Dopplungen sind ja immer dann kritisch, wenn es Änderungen gibt und somit x Stellen überarbeitet werden müssen, da man Gefahr läuft, einige zu vergessen).

Abschließend folgt jetzt noch die beispielhafte Implementierung des Clients mit einer Testausgabe.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
					
#include "MySQLConnection.h"
#include "DBConnectionProxys.h"



// Hauptfunktion der Anwendung ////////////////////////////////////////////////
int main(int argc, char** argv) {
	CReadOnlyDBConnection<CMySQLConnection>	oReadOOnlyConnection;
	CFullAccessDBConnection<CMySQLConnection>	oFullConnection;

	oReadOOnlyConnection.Conncect();
	oFullConnection.Conncect();

	oReadOOnlyConnection.GetData();
	oFullConnection.SetData();

	oFullConnection.Disconnect();
	oReadOOnlyConnection.Disconnect();

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

Ausgabe:

CMySQLConnection::Connect
CMySQLConnection::GetData
CMySQLConnection::SetData
CMySQLConnection::Disconnect
		
Zum Seitenanfang
Zum Inhaltsverzeichnis

© Copyright by Thomas Weiß, 2009 - 2012