Wrapper Fassade Pattern
Motivation
Um eine nicht objektorientierte, low level - API durch eine objektorierte, high level API zu kapseln, wird klassischerweise die Wrapper Fassade verwendet.Der Name beschreibt die Struktur des Architekturpatterns. Einerseits wird versucht ein objektionierten Wrapper um bestehende Funktionen zu legen und andererseits soll eine einfache Fassade die komplexe, native API zugänglich machen.
Die Wrapper Fassade stellt gerne in Frameworks bzw. Bibliotheken die erste Schicht - Abstraktion - über der nativen API dar.
- Das ACE Framework bietet Wrapper Fassaden insbesondere für Sockets, Event Demultiplexing, Prozesse, Threads und deren Synchronisation an.
Die Wrapper Fassade baut auf den zwei klassischen GoF Design Pattern Wrapper und Fassade auf.
Einzelteile
Wrapper - Dekorierer
Das Wrapper Design Pattern ist besser unter dem Namen Dekorierer bekannt.Anwendbarkeit
- ein Objekt dyamisch um Zuständigkeiten erweitern, indem das usprüngliche Objekt transparent mit weiterer Funktionalität dekoriert wird
- schlanke Alternative zur Unterklassenbildung um bestehende Typen zu erweitern
Motivation und Beispiel
- ein Fenster soll mittels
void Fenster::setzeInhalt( VisuelleKomponente* inhalt );
mit Inhalt gefüllt werden
void VisuelleKomponente::show();
...
Fenster* fenster= new Fenster;
fenster->setzeInhalt( new TextAnzeige );
- darüber hinaus soll dieTextAnzeige Komponente optional um einen Rahmen und eine Scrollbar erweitert werden
- der Aufruf von setzeInhalt ist davon abhängig, wie die Funktionalität von VisuelleKomponente erweitert wurde, denn die Implementierung der VisuellenKomponente durch Unterklassenbildung
fenster->setzeInhalte( new RahmenScrollBarTextAnzeige );
unterscheidet sich auf Klientenseite von dem Dekorierer Pattern
fenster->setzeInhalte( new RahmenDekorierer( new ScrollBarDekorierer( new TextAnzeige ) ) );
- zwei Dinge fallen als Nachteil der Unterklassenbildung gegenüber dem Dekorierer Pattern schnell auf
- jede Kombinationsmöglichkeit der Dekorierer muß durch eine entsprechende Unterklasse abgebildet werden
um die Funktionalität von n Dekorieren anzubieten müssen 2^n Unterklassen implementiert werden, ignoriert man das Wiederholen oder die Reihenfolge der Dekorierer bei dieser Teilmengenbetrachtung - die Flexibilität durch Unterklassenbildung beschränkt sich auf die Zeit des Codieren, denn jede Unterklasse muß zur Laufzeit bekannt sein;
hingegen können die verschiedenen Dekorierer zu Laufzeit konfiguriert werden
- jede Kombinationsmöglichkeit der Dekorierer muß durch eine entsprechende Unterklasse abgebildet werden
Struktur
Dekorierer
Wie wirkt nun alles zusammen
statisch
- sowohl die konkrete Komponente ( vgl.TextAnzeige ) wie auch der Dekorierer besitzen die Komponente als Basisklasse auf beiden Typen kann operation() aufgerufen werden
- ein Dekorierer hält sich eine Komponente als Inhalt der Dekorierer dekoriert die Komponente
- die Komponente ist im innersten Fall eine konkrete Komponente, andernfalls ein weiterer Dekorierer
- die konkreten Dekorierer füge in operation() nocht eine spezifische Funktionalität die Dekoration
dynamisch
- Annahme:
- als operation() wähle ich show() mit dem obigen KlassenTextAnzeige und Fenster
- Fenster soll ein Aufruf show() an seinen Inhalt delegieren
- eine einfache Skizze des Codes
void Fenster::setzeInhalt( VisuelleKomponente* inhalt );
void Fenster::show(){ inhalt->show(); }
...
Fenster* fensterSimple= new Fenster;
fensterSimple->setzeInhalt( new TextAnzeige );
Fenster* fensterDecorated = new Fenster;
fensterDecorated->setzeInhalte( new RahmenDekorierer( new ScrollBarDekorierer( new TextAnzeige ) ) );
...
// beide Methoden delegieren ihren Aufruf mittels _inhalt->show()_
fensterSimple->show();
fensterDecorated->show()
- folgende Aufrufskette wird durch die show()Methode der Klasse Kette angestossen
fensterSimple->show()
- inhalt->show() <objOfTypeTextAnzeige> ->show()
fensterDecorated->show()
- inhalt->show() <objOfTypeRahmenDekorierer> ->show() <objOfTypeRahmenScrollBarDekorierer> ->show() <objOfTypeTextAnzeige> ->show()
Verwandte Muster
- Adapter - Anpassung von Schnittstellen :
Der Adapter passt im Gegensatz zum Dekorierer eine Schnittstelle an, während er die Funktionalität erhält. - Kompositum - Transparente Kompositum von Objekthierachien :
Ein Kompositum verwaltet in der Regel mehrere konkrete Komponenten. Das Charakteristium des Dekoriers besteht nicht in der Komposition sondern in der Dekoration der Komponenten. - Strategie - Dynamisches Verändern des Verhaltens :
Die Strategie tauscht über das Strategieobjekt das Innere des Objektes aus, während der Dekorierer eine neue Hülle hinzufügt.
Fassade
Das Fassade Pattern kann man mit dem Interface einer Klasse vergleichen.Während das Interface einer Klasse den Zugriff auf die Instanzen der Klasse koordiniert, so leistet dies eine Fassade typischerweise für die Klassen eines Frameworks.
Der wesentliche Unterschied besteht wohl darin, dass die Fassade keine Einschänkungen auf die Sichtbarkeit des Frameworks leistet.
Klassischerweise sind Fassade lediglich abstrakte Schnittstellen, so daß die Implementierung - das Framework oder die Library - dynamisch ausgetauscht werden kann.
- Struktur
- Anwendung
- um eine einfache Schnittstelle zu einem komplexen Subsystem anzubieten, denn die meisten Klienten verwenden meist nur eine eingeschränkte Teilmenge des Subsystems
- um die lose Kopplung zwischen dem Klient und dem Subsystem zu fördern, denn der Klient hängt nicht von vielen Komponenten des Subsystems ab sondern nur von der Fassade
- um das Subsystem in Schichten zu unterteilen, damit diese nur noch über ihre Fassade miteinander kommunizieren
Beispiel für die Wrapper Fassade ( Server Socket mit C und ACE )
- MainEventLoop eines Server Sockets, der auf einen Port lauscht und für jede eingehende Anfrage in einem neuen Thread einen Logging Handler für diese Anfrage erzeugt.
|
|
- in folgenden Punkten unterscheidet sich der ACE Code vom nativen C Code
- keine bedingtes Kompilieren
- der C Aufruf accept wird durch die Methode accept des SOCK_Acceptor abgebildet Wrapper
- die drei nativen C Aufrufe socket, bind und listen um einen Server Socket auf einen Port binden und lauschen zu lassen werden im Konstruktor von SOCK_Acceptor ausgeführt Fassade
- openim SOCK_Acceptor Konstruktor mit Defaultwerten:
ACE_SOCK_Acceptor (const ACE_Addr &local_sap,
int reuse_addr = 0,
int protocol_family = PF_UNSPEC,
int backlog = ACE_DEFAULT_BACKLOG,
int protocol = 0);
// General purpose routine for performing server ACE_SOCK creation.
int
ACE_SOCK_Acceptor::open (const ACE_Addr &local_sap,
int reuse_addr,
int protocol_family,
int backlog,
int protocol)
{
ACE_TRACE ("ACE_SOCK_Acceptor::open");
if (local_sap != ACE_Addr::sap_any)
protocol_family = local_sap.get_type ();
else if (protocol_family == PF_UNSPEC)
{
#if defined (ACE_HAS_IPV6)
protocol_family = ACE::ipv6_enabled () ? PF_INET6 : PF_INET;
#else
protocol_family = PF_INET;
#endif /* ACE_HAS_IPV6 */
}
if (ACE_SOCK::open (SOCK_STREAM,
protocol_family,
protocol,
reuse_addr) == -1)
return -1;
else
return this->shared_open (local_sap,
protocol_family,
backlog);
}
- mit shared_open
int
ACE_SOCK_Acceptor::shared_open (const ACE_Addr &local_sap,
int protocol_family,
int backlog)
{
ACE_TRACE ("ACE_SOCK_Acceptor::shared_open");
int error = 0;
#if defined (ACE_HAS_IPV6)
ACE_ASSERT (protocol_family == PF_INET || protocol_family == PF_INET6);
if (protocol_family == PF_INET6)
{
sockaddr_in6 local_inet6_addr;
ACE_OS::memset (reinterpret_cast<void *> (&local_inet6_addr),
0,
sizeof local_inet6_addr);
if (local_sap == ACE_Addr::sap_any)
{
local_inet6_addr.sin6_family = AF_INET6;
local_inet6_addr.sin6_port = 0;
local_inet6_addr.sin6_addr = in6addr_any;
}
else
local_inet6_addr = *reinterpret_cast<sockaddr_in6 *> (local_sap.get_addr ());
// We probably don't need a bind_port written here.
// There are currently no supported OS's that define
// ACE_LACKS_WILDCARD_BIND.
if (ACE_OS::bind (this->get_handle (),
reinterpret_cast<sockaddr *> (&local_inet6_addr),
sizeof local_inet6_addr) == -1)
error = 1;
}
else
#endif
if (protocol_family == PF_INET)
{
sockaddr_in local_inet_addr;
ACE_OS::memset (reinterpret_cast<void *> (&local_inet_addr),
0,
sizeof local_inet_addr);
if (local_sap == ACE_Addr::sap_any)
{
local_inet_addr.sin_port = 0;
}
else
local_inet_addr = *reinterpret_cast<sockaddr_in *> (local_sap.get_addr ());
if (local_inet_addr.sin_port == 0)
{
if (ACE::bind_port (this->get_handle (),
ACE_NTOHL (ACE_UINT32 (local_inet_addr.sin_addr.s_addr))) == -1)
error = 1;
}
else if (ACE_OS::bind (this->get_handle (),
reinterpret_cast<sockaddr *> (&local_inet_addr),
sizeof local_inet_addr) == -1)
error = 1;
}
else if (ACE_OS::bind (this->get_handle (),
(sockaddr *) local_sap.get_addr (),
local_sap.get_size ()) == -1)
error = 1;
if (error != 0
|| ACE_OS::listen (this->get_handle (),
backlog) == -1)
{
ACE_Errno_Guard g (errno); // Preserve across close() below.
error = 1;
this->close ();
}
return error ? -1 : 0;
}
- letztendlich landet man auf der nativen C Schnittstelle am Beispiel von accept
ACE_INLINE ACE_HANDLE
ACE_OS::accept (ACE_HANDLE handle,
struct sockaddr *addr,
int *addrlen)
{
ACE_OS_TRACE ("ACE_OS::accept");
#if defined (ACE_PSOS)
# if !defined (ACE_PSOS_DIAB_PPC)
ACE_SOCKCALL_RETURN (::accept ((ACE_SOCKET) handle,
(struct sockaddr_in *) addr,
(ACE_SOCKET_LEN *) addrlen),
ACE_HANDLE,
ACE_INVALID_HANDLE);
# else
ACE_SOCKCALL_RETURN (::accept ((ACE_SOCKET) handle,
(struct sockaddr *) addr,
(ACE_SOCKET_LEN *) addrlen),
ACE_HANDLE,
ACE_INVALID_HANDLE);
# endif /* defined ACE_PSOS_DIAB_PPC */
#else
// On a non-blocking socket with no connections to accept, this
// system call will return EWOULDBLOCK or EAGAIN, depending on the
// platform. UNIX 98 allows either errno, and they may be the same
// numeric value. So to make life easier for upper ACE layers as
// well as application programmers, always change EAGAIN to
// EWOULDBLOCK. Rather than hack the ACE_OSCALL_RETURN macro, it's
// handled explicitly here. If the ACE_OSCALL macro ever changes,
// this function needs to be reviewed. On Win32, the regular macros
// can be used, as this is not an issue.
# if defined (ACE_WIN32)
ACE_SOCKCALL_RETURN (::accept ((ACE_SOCKET) handle,
addr,
(ACE_SOCKET_LEN *) addrlen),
ACE_HANDLE,
ACE_INVALID_HANDLE);
# else
# if defined (ACE_HAS_BROKEN_ACCEPT_ADDR)
// Apparently some platforms like VxWorks can't correctly deal with
// a NULL addr.
sockaddr_in fake_addr;
int fake_addrlen;
if (addrlen == 0)
addrlen = &fake_addrlen;
if (addr == 0)
{
addr = (sockaddr *) &fake_addr;
*addrlen = sizeof fake_addr;
}
# endif /* ACE_HAS_BROKEN_ACCEPT_ADDR */
ACE_HANDLE ace_result = ::accept ((ACE_SOCKET) handle,
addr,
(ACE_SOCKET_LEN *) addrlen);
# if !(defined (EAGAIN) && defined (EWOULDBLOCK) && EAGAIN == EWOULDBLOCK)
// Optimize this code out if we can detect that EAGAIN ==
// EWOULDBLOCK at compile time. If we cannot detect equality at
// compile-time (e.g. if EAGAIN or EWOULDBLOCK are not preprocessor
// macros) perform the check at run-time. The goal is to avoid two
// TSS accesses in the _REENTRANT case when EAGAIN == EWOULDBLOCK.
if (ace_result == ACE_INVALID_HANDLE
# if !defined (EAGAIN) || !defined (EWOULDBLOCK)
&& EAGAIN != EWOULDBLOCK
# endif /* !EAGAIN || !EWOULDBLOCK */
&& errno == EAGAIN)
{
errno = EWOULDBLOCK;
}
# endif /* EAGAIN != EWOULDBLOCK*/
return ace_result;
# endif /* defined (ACE_WIN32) */
#endif /* defined (ACE_PSOS) */
}
- openim SOCK_Acceptor Konstruktor mit Defaultwerten:
Struktur
- Wrapper Fasade:
- die Klasse Wrapper Fassade erweitert die B2BServices? indem sie die method1() bis methodn() anbietet
- diese Methoden legen sich wie ein Wrapper um die Low Level Funktionen function()
- die Methoden method() dienen als OO - Hülle für die nicht OO - Implementierung der Funktionen
- die System API kann als High End Service angeboten werden
Nutzen
Oder Welche Vorteile bieten High EndAPIs?Use Wrapper Facades to Enhance Type Safety
- Entwerfe OO-Klassen, die die richtige Nutzung erzwingen
- I/O Socket Handler sind unter UNIX integer Zahlen und unter Win32 Pointer
- C erlaubt es, streamorientierte ( TCP ) und messageorientierte ( UDP ) Funktionen auf einem Server Socket aufzurufen
- Erlaube die kontrollierte Verletzung der Typesicherheit
- Wrapper sind nur eine Kapselung der Komplexität nicht der Sichtbarkeit der nativen API
- Fehler werden zum Compilezeit und nicht nur Laufzeit entdeckt
- Entwerfe OO-Klassen, die die richtige Nutzung erzwingen
Simplify for the Common Case
- Wie so oft gilt das Pareto 80:20 Prinzip :
80 Prozent der Anwendungen kann mit 20 Prozent der Funktionalität erreicht werden. - Kombiniere mehrere Funktionen in einer Methode
- die vier Schritte
- erzeuge den Socket
- binde den Socket an eine Kommunikationspunkt
- lasse den Socket lauschen
- nimm Verbindungen an und gib ein Handle darauf zurück
einen passiven Server Socket zu erzeugen und zu initialiseren sind aufwändig und fehleranfällig
ACE_SOCK_Acceptor ruft in seiner open() Methode socket(), bind() und listen() explizit auf
- die vier Schritte
- Kombiniere Funktionen hinter einer vereinheitlichten Wrapper Fassade
- Vereinheitliche die unterschiedliche
- Syntax der Funktionennamen ( pthread_create() versus thr_create() versus Create_Thread() )
- Return Werte der Funktionen
- Anzahl der Funktionen
- Aufrufreihenfolge der Funktionen
- Semantik der Funktionen - sofern es möglich ist
- Vereinheitliche die unterschiedliche
- Ordne die Parameter für die Funktionen um und biete Default Argumente an
- die native C API, damit ein Client Socket an eine Server Socket sich konnektiert verlangt viele Argumente um jede möglichen Anwendungsfall abzudecken
- ACE_SOCK_Connector bieten die allgemeinen Defaultwerte in der connect()Methode schon an
int ACE_SOCK_Connector::connect (ACE_SOCK_Stream &new_stream,
const ACE_Addr &remote_sap,
const ACE_Time_Value *timeout = 0,
const ACE_Addr &local_sap = ACE_Addr::sap_any,
int reuse_addr= 0,
int flags = 0,
int perms = 0,
int protocol_family= PF_INET,
int protocol = 0);
- Fasse thematisch zusammenhängende Daten und Funktionen zusammen
- die C Funktionen socket(), bind(), listen() und accept() werden im ACE_SOCK_Acceptor zusammengefaßt
- Wie so oft gilt das Pareto 80:20 Prinzip :
Use Hierachies to Enhance Design Clarity and Extensibility
- Ersetze eindimensionale APIs mit Hierachien
- alle C Funktionen für die Socketprogrammierung liegen in einem, globalem Namensraum
- Socket API
- es ist keine Gruppierung nach den verschiedenene Kommunikationsaspekten möglich
- Kommunikationsbereich
- lokal
- remove
- Kommunikationsrolle
- aktiv
- passiv
- Datentransfer
- Kommunikationsdienst
- Stream
- Datagramm
- Connected Datagram
- Kommunikationsbereich
- Socket API
- im Gegensatz dazu moduliert die ACE Socket Wrapper Fassade entsprechend den verschiedenen Kommunikationsaspekten
- ACE Socket Wrapper Fassade
- ACE Socket Wrapper Fassade
- alle C Funktionen für die Socketprogrammierung liegen in einem, globalem Namensraum
- Ersetze die Pseudo-Ableitung der richtige OO Hierachien
- mittels C Strukturen und Typecasts erzeugte Pseudo-Ableitungen können durch typesichere Hierachien abgelöst werden
// decleare internet socket adress
struct sockaddr_in sock_addr;
...
// configure the socket
sock_addr.sin_family= PF_INET;
sock_addr.sin_port= htons( LOGGING_PORT );
sock_addr.sin_addr.s_addr= htonl( INADDR_ANY );
...
// Associate adress with endpoint.
bind( acceptor, reinterpret_cast< struct sockaddr *>
(&sock_addr),sizeof sock_addr);
- im bind Aufruf wird die Internet Socket Adresse auf die allgemeine Socket Adresse downgecastet simulieren einer Ableitunngshierachie
- mittels C Strukturen und Typecasts erzeugte Pseudo-Ableitungen können durch typesichere Hierachien abgelöst werden
- Ersetze eindimensionale APIs mit Hierachien
Hide Platform Differences Whenever Possible
- Unterstützte das Kompilieren trotzt fehlender Features ( Windwos kennt kein Zombie Konzept )
- je nach Anwendungsfall sollte man eine aussagekräftige Fehlermeldung zum Laufzeit herausgeben oder das Feature ignorieren
- Emuliere fehlende Features auf den ensprechend Plattformen ( Semaphoren sind nicht überall vorhanden, können aber durch Read/Write Locks und ACE Condition Variablen emuliert werden )
- Bilde äquivalente Implementierung mittels parametrisierten Typen ab
- Parametrisierte Typen ( Templates ) erlauben das einfache konfigurieren
// ACCEPTOR depends on the IPC mechanism.
template <class ACCEPTOR>
int echo_server( const typename ACCEPTOR::PEER &addr );
- Parametrisierte Typen ( Templates ) erlauben das einfache konfigurieren
- Unterstützte das Kompilieren trotzt fehlender Features ( Windwos kennt kein Zombie Konzept )
Optimize for Efficiency
- folgende Punkte sind sehr C++/ACE orientiert
- Entwerfe Wrapper Fassaden der Effiziens wegen.
- Viele ACE Wrapper Fassaden sind konkreten Typen und bieten daher folgende Vorteile, denn :
- sie kommmen ohne einen virtuellen Dispatch aus
- sie erlauben dem Compiler Methoden zu inlinen
- Viele ACE Wrapper Fassaden sind konkreten Typen und bieten daher folgende Vorteile, denn :
- Inline zeitkritische Methoden. ( recv() und send() )
Bekannte Anwendungen
Die Wrapper Fassade wird sehr oft eingesetzt um Low Level APIs mittel OO - Mitteln zu kapseln und zu nutzen.OO Sprachen, die in C implementiert sind
Python
Java ( Java Virtual Machine, AWT und Swing )
Frameworks
ACE
MFC
Weiterlesen...