Inhaltsverzeichnis[Anzeigen]

 

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

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 );
    void VisuelleKomponente::show();
    ...
    Fenster* fenster= new Fenster;
    fenster->setzeInhalt( new TextAnzeige );
    mit Inhalt gefüllt werden
  • 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 ) ) ); 
  • TIPzwei Dinge fallen als Nachteil der Unterklassenbildung gegenüber dem Dekorierer Pattern schnell auf
    1. jede Kombinationsmöglichkeit der Dekorierer muß durch eine entsprechende Unterklasse abgebildet werden
      MOVED TO... 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
    2. 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

Struktur

Dekorierer

decorator.gif

Wie wirkt nun alles zusammen

statisch
  • sowohl die konkrete Komponente ( vgl.TextAnzeige ) wie auch der Dekorierer besitzen die Komponente als Basisklasse MOVED TO... auf beiden Typen kann operation() aufgerufen werden
  • ein Dekorierer hält sich eine Komponente als Inhalt MOVED TO... 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 MOVED TO... 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() more <objOfTypeTextAnzeige> ->show()
    • fensterDecorated->show()
      • inhalt->show() more <objOfTypeRahmenDekorierer> ->show() more <objOfTypeRahmenScrollBarDekorierer> ->show() more <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
    fassadeOMT.gif
  • 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.

 

 

  • CVariante
    // Main driver function for the server.
    int main( int argc, char *argv[] ){
    struct sockaddr_in sock_addr;
    // Handle UNIX/Win32 portabilility.
    #if defined (_WIN32)
    SOCKET acceptor;
    WORD version_request = MAKEWORD( 2,0 );
    WSADATA wsa_data;
    int error = WSAStartup( version_requested, &wsa_data);
    if ( error != 0 ) return -1;
    #else
    int acceptor;
    #endif
    // Create a local endpoint of communication.
    acceptor= socket ( AF_INET, SOCK_STREAM ,0);
    // Set up the adress to become a serve.
    memset( reinterpret_cast <void *> (&socket_addr),
    0, sizeof sock_addr);
    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);
    // Make endpoint listen for connections.
    listen (acceptor,5);
    // Main server event loop.
    for (;;){
    // Handle UNIX/Win32 portability.
    #if defined (_WIN32)
    SOCKET h;
    DWORD t_id;
    #else
    int h;
    thread_t t_id;
    #endif
    // Block waiting for client to connect.
    h= accept( acceptor, 0, 0);




    // Spawn a new thread that runs the <server>
    // entry point.
    #if defined (_WIN32)
    CreateThread( 0,0,
    LPTHREAD_START_ROUTINE("logging-handler);
    reinterpret_cast <void*>(h), 0, &t_id);
    #else
    thr_create
    ( 0,0, logging_handler,
    reinterpret_cast <void *>(h),
    THR_DETACHED, &t_id);
    #endif
    }
    return 0;
    }
  • ACEVariante
    // Main driver function for the server.
    int main( int argc, char *argv[] ){
    INET_Addr addr( port );

    // Passive-mode acceptor object.
    SOCK_ACCEPTOR server(addr);
    SOCK_STREAM new_stream;



















    // Main server event loop.
    for(;;){








    // Accept a connection from a client.
    server.accecpt( new_stream);

    // Get the underlying handle.
    SOCKET h= new_stream.get_handle();

    // Spawn off a thread-per-conection.

    thr_mgr.spawn
    ( logging_handler
    reinterpret_cast <void *>(h);
    THR_DETACHED);
    }






    return 0;
    }

 

  • 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 MOVED TO... 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 MOVED TO...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) */
        }

Struktur

  • Wrapper Fasade:
    WrapperFacade.gif
  • 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
  • MOVED TO... 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
    • MOVED TO... Fehler werden zum Compilezeit und nicht nur Laufzeit entdeckt
  • 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
        1. erzeuge den Socket
        2. binde den Socket an eine Kommunikationspunkt
        3. lasse den Socket lauschen
        4. 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
          MOVED TO... ACE_SOCK_Acceptor ruft in seiner open() Methode socket(), bind() und listen() explizit auf
    • 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
    • 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
  • 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
          SocketNative.jpg
        • MOVED TO...es ist keine Gruppierung nach den verschiedenene Kommunikationsaspekten möglich
          • Kommunikationsbereich
            • lokal
            • remove
          • Kommunikationsrolle
            • aktiv
            • passiv
            • Datentransfer
          • Kommunikationsdienst
            • Stream
            • Datagramm
            • Connected Datagram
      • im Gegensatz dazu moduliert die ACE Socket Wrapper Fassade entsprechend den verschiedenen Kommunikationsaspekten
        • ACE Socket Wrapper Fassade
          SocketACE.jpg
    • 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 MOVED TO... simulieren einer Ableitunngshierachie
  • 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 );
  • 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
    • 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

Mentoring

Stay Informed about my Mentoring

 

Rezensionen

Tutorial

Besucher

Heute 560

Gestern 3357

Woche 12123

Monat 39440

Insgesamt 3892154

Aktuell sind 48 Gäste und keine Mitglieder online

Kubik-Rubik Joomla! Extensions

Abonniere den Newsletter (+ pdf Päckchen)

Beiträge-Archiv

Sourcecode

Neuste Kommentare