ArchitekturPattern

  •  

    Broker

    Der Broker bezeichnet im Broker Pattern sowohl die zentrale Rolle in diesem Architekturpattern als auch das Gesamtpattern.

    Um was gehts

    Das Broker Pattern dient der Strukturierung verteilter Softwaresystem mit entkoppelten Komponenten. Ein Broker ist für die Kommunikation der Komponenten verantwortlich.

    Beispiele

     

    Problem

    Ein komplexes Softwaresystem soll als Menge von entkoppelten und interagierenden Teilsystemen entworfen werden.
    Daraus resultieren folgende Konsequenzen: die Teilsysteme sollten
    • miteinander kommunizieren right Kommunikationsprotokoll
    • sich finden right Namensdienst
    • zur Laufzeit konfiguriert werden right Konfigurationsmanagement
    • einfach ansprechbar right definierte API

    Lösung

    Führe einen Broker ein, der den Dienstanbieter und den Dienstnutzer zusammenbringt. Die Dienstanbieter registrieren sich beim Broker. Der Client stellt eine Anfrage an den Broker, der ihm dann den Serviceanbieter vermittelt.
    Der Broker stellte die Infrastruktur für das kommunizieren, das finden und das konfigurieren der Teilssysteme über eine einfache API bereit.

    Struktur

    Das Broker Architektur Pattern besteht aus sechs Komponenten: Clienten, Servern, Vermittler, Brücken, Stellvertreter auf Client und Server-Seite.
    • Broker Architektur Pattern:
      BrokerPattern.JPG
    Die Komponenten im Detail:

    Server

    Der Server stellt die Funktionalität zur Verfügung, die durch eine Schnittstellen-Definitionssprache spezifiziert wird. Hier muß man zwischen zwei Servertypen unterscheiden:
    • Server, die Funktionalität für die Broker Architektur implementieren ( Namensdienst )
    • Server, die die spezifische Funktionalität zur Verfügung stellen

    Clienten

    Anwendungen, die Funktionalität der Server nutzen, indem sie dem Vermittler Anfragen stellen. Die klassische Trennung Client - Server existiert hier nicht, da Server für die Bearbeitung ihrer Anfrage anderere Server nutzen können (müssen).

    Vermittler

    Bringt den Dienstanbieter und den Dienstnutzer zusammen und stellt ihnen einen Kommunikationskanal zur Verfügung. Der Vermittler nutzt zur Auffindung des Service den Namensdienst. Typischerweise besitzt er eine Registratur für die Services und die Mächtigkeit, den Lebenszyklus der Services zu steuern. Serviceanfragen, die er nicht an seine Services delegiert, leitet er gegebenfalls an anderer Vermittler mittels einer Brücke weiter.

    clientseitige Stellvertreter ( Stub )

    Dieser Proxy befindet sich transparent zwischen dem Clienten und dem Vermittler. Seine Aufgabe besteht im wesentlichen darin, systemspezifsche Funktionalität zu kapseln. Dies kann die Verwaltung des Speichers, der IPC-Mechnismus, das Marshallen oder das Cachen von Daten sein. Oft findet im clientseitigen Proxy eine Abbildung vom Objektmodell des Client zu dem des Vermittlers statt.

    serverseitige Stellvertreter ( Skeleton )

    Der Server Proxy erfüllt die ähnlichen Aufgaben wie der Client Proxy. So übersetzt er die Daten in das Objektformat des Servers.

    Brücke

    Dies Brücke kapselt netzwerkspezifische Funktionalität, damit Vermittler verschiedener Brokerarchitekturen miteinander kommunizieren können.

    Wenn die Proxys das gleiche Protokoll benutzen, ist es möglich, beide direkt miteinander kommunizieren zu lassen. Dies bezeichnet man auch als direkte Kommunikation, während die indirekte Kommunikation immer über den Vermittler erfolgt.

    Dynamische Aspekte

    Server registriert sich beim Vermittler

    • der Vermittler startet beim initialisieren des Systems und enters its event loop
    • der Server startet, initialisiert sich und meldet sich beim Vermittler an
    • der Vermittler nimmt die Registratur des Servers entgegen, speichert sie und schickt dem Server eine Bestätigung
    • der Server erhält die Bestätigung und enters its event loop

    Client sendet eine Anfrage an den Server

    • der Client setzte beim Starten eine Anfrage an den Server ab
    • der Client-Stub externalisiert ( marshaling oder pack ) die Daten und leitet sie an den lokalen Vermittler weiter
    • der Vermittler schaut in seiner Registratur nach und schickt die Anfrage an den Server-Skeleton weiter
    • der Server-Skeleton internalisiert ( demarshaling oder unpack ) die Nachricht und ruft den Server auf
    • die durch den Server bearbeitete Anfrage wird über den Server-Proxy ( Server-Skeleton ) externalisiert und an den Broker zurückgeschickt
    • der Vermittler sendet die Nachricht an den Client-Proxy ( Client-Stub )
    • der Client-Proxy internalisiert das Ergebnis und schicket es an den Client, die Applikation

    Kommunikation zwischen verschieden Broker Architekturen

    • Vermittler A erhält eine Anfrage und stellt fest, daß der Vermittler B im Gegensatz zu ihm einen entsprechenden Server registriert hat
    • Broker A schickt die Nachricht an Broker B, nachdem er die Nachricht von seinem Protokoll in ein gemeinsames Protokoll übersetzt hat
    • Broker B nimmt die Nachricht in dem gemeinsamen Protokoll an und übersetzt sie in sein spezifisches Protokoll
    • ...
    Gerade die Kommunikation zwischen zwei verschieden Brokerarchitekturen setzt voraus, daß jede Broker Architektur mindestens ein internes und externes Protokoll versteht.

    Implementierung

    1. Lege das Objektmodell fest
      • Sollen die Serverobjekte zustandslos oder zustandsbehaftet sein?
      • Welche Operationen sollen sie unterstützen?
      • Wie werden sie instanziiert oder gelöscht?
      • Welches Interface benötigen sie intern, zum Broker oder zum Clienten?
      • Welche Datentypen, Fehlermeldungen sollen sie unterstützen? Ein sehr ausgereiftes Modell besitzen DeWikipedia:Enterprise_Java_Beans .
    2. Stelle die Komponenten-Interoperabilität sicher
      • Unterstützung eines Binärstandars
        • in DeWikipedia:OLE_%28EDV%29 stehen binäre Methodentabellen zur Verfügung, die Zeiger auf die Implementierung der Methode enthalten
      • Einführung eines Schnittstellenbeschreibungssprache DeWikipedia:Interface_Definition_Language
        IDL bezeichnet sowohl die Interface Definition Language als auch deren konkrete Implementierung in CORBA
        • sun RPC
          • das Programm rpcgen erzeugt aus einer Schnittstellenbeschreibung insbesondere Stubs, Skeletons und eine XDR Datei zur Datenkonvertierung
          • rpcgen stellt eine API für entfernte Funktionsaufrufen in Czu Verfügung
            square_instruct square_in { /* input (argument) */
            long arg1;
            };

            struct square_out { /* output (result) */
            long res1;
            };

            program SQUARE_PROG {
            version SQUARE_VERS {
            square_out SQUAREPROC(square_in) = 1; /* procedure number = 1 */
            } = 1; /* version number */
            } = 0x31230000; /* program number */
        • Remote Methode Invocation: RMI
          • der rmic Compiler erzeugt aus einem Serverinterface die Stubs und Skeletons in Java
          • im Gegensatz zu den Funktionsreferenzen beim sun RPC sind dies Objektreferenzen
          • seit Java 5 wird der Stub und Skeleton implizit von der Java Virtual Maschine erstellt
            public interface ServerInterface extends Remote
            {
            public void method() throws RemoteException;
            }
        • CORBA
          • CORBA benutzt die IDL für die Interfacebeschreibung
          • war die Interfacebeschreibung in sun RPC C orientiert, so ist die CORBA IDL C++ orientiert
          • CORBA verbindet mit RMi, daß hier Objekte referenziert werden
          • standardisierte Implementierung eines IDL2Language Compilers gibt es für Ada, C, C++, Lisp, Smalltalk, Java, COBOL, PL/I und Python
            module BankSimple {

            interface Account {

            // Operations available on the account.
            void deposit (in float amount);
            void withdraw (in float amount);
            };
            };
      • Web Services
        • die Interfacebeschreibungssprache nennt sich hier Web Service Definition Language, kurz wsdl
        • dieser xml Dialekt ist deutlicher komplexer: DeWikipedia:WSDL
        • im Gegensatz zu den bisherigen IDL's ist die wsdl nicht nur rein deklarativ, sondern sie legt die Art der Daternübertragung und den Servicepunkte fest
        • Implementierung eines wsdl2Compiler Codegenerators gibt es in Java, C++, Python, Perl,...

          Sun RPC stellt als einzige dargestellte IDL nur Funktionreferenzen zur Verfügung. Alle anderen bieten Objektreferenzen an. Der wesentliche Unterschied zwischen sun RPC und RMI auf der einen und CORBA und wsdl auf der anderen Seite besteht darin, daß die ersten Lösungen für eine Sprache anbieten. Die wsdl unterscheidet sich von allen anderne IDL#s darin, daß sie über den rein deklarativen Charakter hinausgeht.
    3. Spezifiziere das Interface des Vermittlers
      • zu den Clienten
        • für den Clienten existieren zwei Möglichkeiten Anfragen abzusetzen
          • statisch
            • der Client weiß ganz genau, welches Services der Vermittler unterstützt
            • für die Applikation bedeutet dies, daß sie ihren Aufruf gegen den statisch erzeugen Stub absetzt
            • der Vermittler nützt seinen Namensdienst und findet eine eindeutige Abbildung von angefragen Dienst an den Dienstanbieter
            • Nutzung eines Nameing Service
          • dynamisch
            • besitzt der Client nur ein ungefähre Ahnung, nützt der Vermittler Reflektion um den Dienstanbieter ausfindig zu machen
            • Nutzung eines Trading Service
      • zu den Servern
        • als Ansprechpartner für die Services benötigt der Broker Schnittstellen um die Serverobjekte zu verwalten
          • das anmelden, abmelden, identifizieren, relokieren, starten und überwachen von Services sind denkbare Aufgaben für den Vermittler
          • entscheidend für die Schnittstelle ist die Frage, ob man Services statisch oder dynamisch verwaltet
          • im statischen Fall kann es für den Vermittler ausreichend sein, eine Konfigurationsdatei über alle verfügbaren Services während seiner Initialisierung einzulesen
    4. Führe Proxys für die Clienten und Server ein
      • so wie der Clientstub für den Clienten den Server repräsentiert, so repräsentiert der Serverskeleton den Client für den Server
      • die beiden Proxy helfen insbesondere, das serialisieren und deserialisieren der Anfragen und Antworten der beiden Kommunikationsendpunkten zu verbergen
      • durch die Stellvertreter ist es insbesondere möglich, Interoperatibilität zwischen verschiedenen Sprachen zu erreichen
      • sowohl Client und Stub wie auch Server und Skeleton laufen im gleichen Prozeß
    5. entwickle den Vermittler parallel zu den letzten zwei Punkten
      Aus Performancegründen kann der Vermittler nur die Verbindung zwischen den Endpunkten einrichten, während der weitere Datenaustausch direkt zwischen dem Clienten und dem Servern, bzw. genauer zwischen dem Stub und dem Skeleton erfolgt. Diese Variante des Broker Patterns wird auch als direktkommunizierende Broker-Systemebezeichnet.
      1. lege das Kommunikationsprotokoll für lokale Brokerarchitektur fest
      2. lege ein Brücke für die Interoperatibilität mit andern Broker Architekturen fest
        • mit CORBA 2.0 wurde GIOP bzw. sein Implementierung über TCP/IP IIOP spezifiziert, damit verschiedene Broker - Architekturen miteinander kommunizieren können
        • spricht die lokale Broker Architektur eine anders Protokoll, sollte man gegebenfalls eine Abbildung von diesem lokalen Protokoll nach IIOP implemtieren
      3. führe ein Verbindungscookieein
        • falls die Serviceanfrage und die Serviceanwort über den Vermittler über den Vermittler geleitet werden, ist es notwendig, daß dieser ein Gedächtnis erhält, damit er die Zuordnung der Serviceantwort zum entsprechend Client vollziehen kann
        • dies Gedächtnis ist bei der direkten Variante nicht notwendig, daß hier die zwei Stellvertreter direkt kommunizieren
      4. implementiere gegebenfalls einen Nachrichtenpuffer
        • im Falle einer asynchronen Anfrage ist es notwendig, daß die Ergebnisdaten auf den Serverseite gespeichert werden, bevor sie an den Client übermittelt werden können
      5. entwerfe eine Fehlerbehandlung
        • unterscheide zwischen Kommunikationsfehler und Servicefehlern
        • Welche Aktivitäten soll der Vermittler einleiten, wenn die Kommunikations mit anderen Clienten, Vermittlern oder Servern mißlingt?
        • Soll eine Aktion durch den Vermittler einmal oder bis zum Erfolgsfall angestossen werden.
    6. Entwerfe einen IDL - Übersetzer
      • für jede zu unterstützende Sprache ist eine entsprechende Abbildung der Interfacebeschreibung auf die Implementierungssprache notwendig

    Auswirkungen

    Vorteile

    • Standortunabhängigkeit der Client und Server durch den Vermittler
    • der Client ist unabhängig gegenüber Implementierungsänderungen des Servers
    • Änderungen der IDL könnnen leicht umgesetzt werden, so daß im Idealfall nur leichte Anpassungen auf Server- und Clientseite vollzogen werden müssen
    • leichte Portierbarkeit des Broker-Systems auf neue Systeme, da Client und Server frei von systemspezifischer Funktionalität sind
    • definierte Erweiterungspunkte für neu zu unterstützende Sprachen durch die Implementierung eines IDL2Language- Compilers, der für einzubindende Clienten Stubs und für einzubindende Server Skeletons erzeugen muß; dies schließt inbesondere die Umsetzung der IDL Typen auf die Sprachentypen ein
    • durch die gemeinsame Sprache IIOP der Brokersysteme, können diese miteinander kommunizieren; CORBA und RMI sprechen beiden IIOP
    • neue Services können leicht implementiert werden, da sie Basisservices des Broker Systems nutzen können

    Nachteile

    • Performanceverlust, denn insbesondere die Abstraktion eines Vermittlers stellt eine Indirektion dar
    • die Kommunikation der Parteien hängt von vielen Komponenten ab
     
  •  

    Pipes and Filters

    Um was gehts

    Das Pipes and FiltersArchitekturpattern beschreibt die Struktur für Systeme, die Datenströme verarbeiten.
    • Pipe and Filters:
      PipesFilters.png

    Beispiele

    • TCP/IP Stack
    Jede einzelne Layer umgibt ihre Daten beim Verschicken mit der Headerinformation, die dieser Layer beim Empfangen der Daten entfernt.
    encapsulation.gif
    Das Pipes and Filters Muster ergänzt das LayersPattern auf natürliche Art, interpretiert man die Schichten als Filter und den Datenfluß als Pipe.
    • Unix Pipes
      find -name "*.py" -type f | xargs wc -l | sort | less  
    • Python Strings
      >>> source="dies ist ein Dest"
      >>> senke=source.capitalize().replace("dest","Test")+"."

      >>> print senke
      Dies ist ein Test.

    Problem

    Ein Eingabestrom soll manipuliert werden. Dies kann nicht in einem Transformationsschritt implementiert werden, da
    • mehrere Entwickler beteiligt sind.
    • mehrere Einzelschritte notwendig sind.
    • die Anforderungen sich ändern werden.
    HANDFolgende Punkte müssen bei der Planung des Gesamtsystems berücksichtigt werden
    • der Austausch von einzelnen Prozessschritten
    • einzelne Prozesskomponenten ansprechen
    • kleine, klar definierte Prozessschritte
    • nur benachbarte Filter sollten Daten austauschen
    • verschiedene Datenquellen und -senken unterstützen
    • Parallelverarbeitung zu ermöglichen

    Lösung

    Strukturiere dein Gesamtsystem in Layers. Verbinden jede benachbarte Filter (Layer) mit einer Pipe (Datenfluß). Implementiere für jeden Filter einen Ein- und Ausgabekanal.

    Struktur

    Filter

    • sind die verarbeitenden Komponenten des Systems
    • er modifiziert die Daten
    • reagiert auf Ereignisse
      • der nachfolgende Filter entnimmt Daten right pull Prinzip
      • die vorhergehende Filter übergibt Daten right push Prinzip
      • läuft in einer Schleife, indem er sein Daten einer Queue entnimmt
    • während die ersten zwei Varianten passiv sind, entnimmt der Filter die Daten aktiv aus der Queue und stellt sie dort auch wieder zu Verfügung

    Pipe

    • aktive Filter werden gerne über die Pipe synchronisiert, da der Filter erst wieder bei gefüllter Queue seine Arbeit aufnimmt right Producer Consumer Pattern

    Datenquelle

    • liefert eine Folge von Daten derselben Struktur

    Datensenke

    • sammelt die Ergebnisse zusammen

    Datenfluß

    Es existieren mehrere Möglichkeiten den Datenfluß zu steuern.
    1. push Prinzip
      • die Filteraktivität wird durch die Übergabe der Daten des vorherige Filters gestartet
      • der (n-1)-te Filter schickt (write Operation) Daten an den n-ten Filter
      • Datenquelle als Initiator
    2. pull Prinzip
      • die Filteraktivität wird durch das Anfordern von Daten des nachfolgenden Filters gestartet
      • der n-te Filter fordert (read Operation) Daten vom (n-1)-ten Filter
      • durch sukzessives anfordern vom Daten werden diese letztendlich von der Datensquelle geliefert, so daß die Prozessierung ähnlich dem push Prinzip vollzogen werden kann
      • Datensenke als Initiator
    3. gemischtes push/pull Prinzip
      • der n-te Filter fordert einerseits explizit die Daten vom (n-1)-ten Filter an übergibt sie dann auch explizit an den (n+1)-ten Filter
      • der n-te Filter ist der einzige aktive Filter in der Verarbeitungskette
      • n-te Filter als Initiator
    4. aktive Filter als eigenständige Prozesse
      • jede Filter stellt einen eigenständigen Prozeß/Thread dar, der Daten von der vorherigen Queue liest oder auf die folgende Queue schreibt
      • der n-te Filter kann erst Daten lesen, wenn der (n-1)-te Filter Daten auf die verbindende Queue geschrieben hat
      • der n-te Filter kann erst seine Daten schreiben, wenn der (n+1)-Filter die verbindende Queue gelesen hat
      • die Struktur ist unter dem Namen Producer/Consumer bekannt
      • jeder Filter kann Initiator sein

    Implementierung

    1. teile die Gesamtaufgabe in Einzelschritte
      • jede Stufe darf nur von der vorherigen Stufe abhängen
      • definiert man die Stufe durch eine Interface erreicht man maximale Flexibilität
    2. definiere für jeden Datenkanal das Format der zu verarbeitenden Daten
      • definiere insbesondere ein eindeutiges Zeichen für das Ende der Eingabe
    3. entscheide, wie die Kanäle implementiert werden
      • von passiven Filtern werden die Daten entweder ( pull Prinzip ) angefordert oder diese werden im übergeben ( push Prinzip )
      • explizite Datenkanäle wie Pipes, Fifos oder Queues erlauben im Zusammenhang mit aktiven Filtern größere Flexibilität, denn zwischen den verbunden Producern und Consumern kann eine n:m Beziehung umgesetzt werden
      • will man die Reihenfolge der Filter beliebig permutieren, setze diese gleiche Datenkanäle voraus
    4. entwerfe und implementiere die Filter
      • für passive Filter bieten sich write Funktionen ( push Prinzip) oder read Funktionen ( pull Prinzip ) an
      • aktive Filter werden gerne durch eigene, leichtgewichtige Prozesse implementiert
        • der Durchsatz des Gesamtsystems hängt insbesondere vom Kontextwechsel zwischen den Prozessen und der Puffergröße des Datenkanals ab
      • durch die Parametrierung der Filter wird eine bessere Wiederverwendung ( vgl. sed Editor ) erreicht
    5. entwerfe die Fehlerbehandlung
      • Mehrere Fragen müssen beantwortet werden.
        • In welchem Zustand ist ein Datensatz, falls er die Filter nur teilweise durchlief?
        • Ist ein Datensatz korrumpiert, läßt sich dann die Filterkette neu starten?
        • Sollen Ausnahmen in Filtern korrigiert oder ignoriert werden?
        • Wie bleiben die Daten im definierten Zustand, da sie gleichzeitig von mehreren Prozessen bearbeitet werden können?
      • Die Schwierigkeit der Fehlerbehandlung liegt darin begründet, daß es keinen globalen Zustand der Pipeline-Komponenten gibt, sondern nur Unterzustände der Kanäle und Filter.
    6. stelle die Verarbeitungs-Pipeline zusammen
      • ein Kommandointerpreter vereinfacht die Zusammenstellungs des Workflows (vgl. Command Shell)

    Auswirkungen

    Vorteile

    • Daten werden unmittelbar weitergeleitet HAND keine Zwischendateien notwendig
    • Filter einer Stufe implementieren das gleiche Interface HAND Austausch von Filtern möglich
    • Filter bieten das gleiche Interface an HAND Neuordung oder Ergänzung der Filterkette
    • Filter besitzen ein universelles Interface HAND Wiederverwendung der Filter und Rapid Prototyping
    • Filter läuft in einem eigenen Prozeß HAND Performancegewinn

    Nachteile

    • Nutzung von Zustandinformation ist teuer und unflexibel
    • Parallelverarbeitung ist teuer
      • Übertragung der Daten ist aufwändiger als deren Bearbeitung
      • manche Filter können nicht inkrementell arbeiten, sondern benötigen die ganze Datenmenge ( vgl.sort )
      • der Kontextwechsel zwischen den Prozessen ist teuer
      • falls der Puffer des Datenkanals zu klein ist, muß der Filter häufig gestartet werden
    • um das universelle Interface zu unterstützen werden die Daten häufig formatiert (z.B.: Datenflußformat in)
    • schwierige Fehlerbehandlung

    Anwendungen

    • Streams
    • UNIX Command Shell
    • ACE Stream Framework
  •  

    Reactor Pattern

    Um was gehts

    Das Reaktor Pattern erlaubt es, einer ereignis-getriebenen Anwendung eine oder mehrere Client Anfragen gleichzeitig anzunehmen und auf verschiedene Serviceanbieter zu verteilen ( demultiplex and dispatch ) .

    Auch bekannt unter

    • Dispatcher
    • Notifier

    Beispiel

    • Ein Logging Server erhält mehrere Anfragen gleichzeitig und muß sie auf die verschieden Ausgabegeräte verteilen:
      NetworkLogging.gif
    • Eventhandling ins GUIs

    Anforderungen

    Ein Server soll mehrere Clientanfragen gleichzeitig beantworten können. Jede Clientanfrage besitzt eine eindeutige Indikation, die sie einem Serviceprovider zuordnen läßt. Folgende Bedingungen müssen für die Applikation gewährleistet sein:
    • sie soll nicht blockieren
    • sie soll auf maximalen Durchsatz ausgelegt sein und somit unnötiges Kontext wechseln, Daten synchronisieren oder Daten kopieren vermeiden
    • sie soll einfach um neue und verbesserte Service erweitert werden können
    • sie soll ohne komplexe multithreading und synchronisations Mechanismen auskommen

    Lösung

    Führe für jeden Servicetyp, den die Applikation anbietet, einen Handler ein. Dieser Eventhandler verarbeitet die spezifische Clientanfragen. Registriere den Handler beim Reaktor, der einem synchronen Event Verteiler (event demultiplexer) unterhält um auf eingehende Events zu reagieren. Wenn ein Event im synchronen event demultiplexerauftritt, benachrichtigt dieser den Reaktor, der das Event auf den angefragen Service verteilt.

    Struktur

    Handles

    • identifizieren Eventquellen wie Sockets, Filehandles oder Timersignale des OS-Systems
    • die Eventquellen erzeugen Events wie connect, read, time-out an, die auf den assoziierten Handle geschoben werden
    • der Handle kann nur die entsprechende Operation vollziehen

    synchrone event demultiplex

    • der Verteiler (demultiplexer) wartet auf Indikatoren (indication events), die auf einer Menge von Handles auftreten
    • bis die indication events abgeholt werden, blockiert der event _demultiplexer
    • auf dem assozierten Handle kann nun das eintreffende Ereignis aufgerufen

    Event Handler

    • definiert das Interface um indication events zu prozessieren
    • deklarieren die Services der Applikation

    Konkrete Event Handler

    • verarbeiten indication events in einer applikationsspezifischen Art
    • definieren die Services der Applikation

    Reaktor

    • stellt ein Interface zu Verfügung, damit die Event Handler inklusiver ihrer assozierten Handles registrieren und entfernen kann
    • der Reaktor benützt den synchronen Verteiler (event demultiplexer) um auf die Indikatoren (indicaton events) der Handles zu warten
    • beim Auftreten eines Indikators (indication events) ordnet der Reaktor dies Ereugnis dem entsprechenden Ereignis Handler zu
    • nach der Zuordnung des Ereignis an den entsprechenden Ereignis Handler, ruft ( dispatch ) der Reaktor die assozierte Methode auf dem Event Handler auf
    • der Reaktor startet und unterhält die event loop der Applikation
    Nicht die Applikation, sonder der Reaktor wartet auf indication events, die er auf die entsprechenden konkreten Event Handler verteilt ( demultiplex ) und dann deren assozierte Hook Methode aufruft ( dispatch ). Als Applikationsentwickler gilt es die spezifischen Event Handler zu implementieren und sie beim Reaktor zu registrieren.
    Der Reaktor als Framework stellt eine Ablaufumgebung für die Eventverarbeitung bereit. Diese inversion of control - die Applikation wird durch den Reaktor gesteurt - wird als Hollywood Prinzip bezeichnet.
    Don't call me, we call you.

    • Reaktor Klassendiagramm:
      reactorClassDiagram.gif

     

    Umsetzung

    Timer mit twisted

    Der Reaktor reagiert auf Zeittakte. Diese Ereignisse werden auf die registrierten Handler abgebildet. Sobald die Eventloop des Reaktors mittels reactor.run() gestartet wird, können die Ereignisse verarbeitet werden.
    import time

    from twisted.internet import task
    # http://twistedmatrix.com/trac/browser/trunk/twisted/internet/task.py
    from twisted.internet import reactor

    # http://twistedmatrix.com/trac/browser/trunk/twisted/internet/reactor.py

    # define handler as object
    class Handler():

    def __init__(self, Id ):

    self.__id= Id

    def __call__(self):

    print "Handler with id %s" % self.__id
    print "at %d:%d:%d%s\n" %(time.localtime()[3:6] + ( str(time.time() % 1)[1:] ,))


    # register handler as callable object
    l1 = task.LoopingCall(Handler(1))
    # start the task

    # start calls implicit reactor.callLater(... ) to reschedule the task ( fire the time event )
    l1.start(0.3) # call every 0.3 seconds

    l2 = task.LoopingCall(Handler(2))

    l2.start(1) # call every second

    # running the event loop
    reactor.run()

    Timer mit ACE

    • starte zwei Timer cb1 und cb2, die durch Signale SIGTSTP und SIGINT um den Faktor 10 abgebremst werden können
    Die ACE Variante ist deutlicher verboser, da hier auf Time- und Signalevents mit den entsprechenden CB und Signalhandler reagiert wird. Insbsondere sorgt der TimerDispatcher für das explizite Feueren der Timeevents.
    Durch die schedule Methode des Timers bzw. die register_handler des Reactors werden die Handler registriert. Während der CB Handler auf Timeevents mit handle_timeout reagiert, reagiert der Signalhandler auf Signalevents mit handle_signal. Beide Eventhandler werden durch die gleichen Verteiler bedient ( select ). Die ACE_Timer_Queue bzw. der konkrete Implementierung ACE_Timer_Heap merkt sich die die zukünftigen Zeitpunkte, zu denen sie expire an den Verteiler schickt.
    Die Methode wait_for_event startet die Eventloop, die dann auf die Timer- und Signalevents reagiert.
    #include "ace/Timer_Queue.h"
    #include "ace/Timer_Heap.h"
    #include "ace/Reactor.h"
    #include "CB.h"

    #include "SignalHandler.h"
    #include "TimerDispatcher.h"

    int main()
    {

    CB cb1, cb2;
    cb1.setID(1);
    cb2.setID(2);

    int arg1 = 1, arg2 = 2;

    ACE_Timer_Queue *timer_queue;

    ACE_NEW_RETURN(timer_queue, ACE_Timer_Heap, -1);

    // setup the timer queue

    Timer::instance()->set(timer_queue);

    ACE_Time_Value curr_time = ACE_OS::gettimeofday();

    ACE_Time_Value threeSeconds = curr_time + ACE_Time_Value(3L);
    ACE_Time_Value fourSeconds = curr_time + ACE_Time_Value(4L);

    // start in 3 seconds, each second
    long timerId1= Timer::instance()->schedule(&cb1, &arg1, threeSeconds, ACE_Time_Value(1));

    // start in 4 seconds; each 0.3 secondcs
    long timerId2=Timer::instance()->schedule(&cb2, &arg2, fourSeconds, ACE_Time_Value(0,300000));


    // Strg c
    SignalHandler *mutateTimer1= new SignalHandler( timerId1 );

    // Strg z
    SignalHandler *mutateTimer2= new SignalHandler( timerId2 );

    ACE_Reactor::instance()->register_handler( SIGINT, mutateTimer1);
    ACE_Reactor::instance()->register_handler( SIGTSTP, mutateTimer2);


    // "run" the timer.
    Timer::instance()->wait_for_event();

    return 0;

    }

     

    Dynamische Aspekte

    • Die Applikation registriert einen konkreten Eventhander beim Reaktor. Der Eventhandler drückt durch sein Implementierung aus, auf welche Art von Events er reagieren will. Typischerweise heißen die hook-Methoden handle_*, wie handle_input, handle_timeout, handle_put,... .
    • Durch eine get_handle des konkreten Eventhandlers Methode holt sich der Reaktor den spezifischen Handler.
    • Wenn alle Handles registriert sind, startet die Applikaton die Eventloop des Reaktors. Der Reaktor überwacht nun die Menge aller registrierten Handler auf das Eintreffen von indication Events .
    • Sobals ein Event auftritt, übergibt der synchrone event demultiplexer die Kontrolle an den Reaktor.
    • Der Reaktor benützt die Handles als Schlüssel, um die entsprechenden Eventhandler aufzurufen (demultiplex) und auf die hook Methode auszurufen (dispatch).
    • Die spezifische Methode des Eventhandler bearbeitet die Anfrage direkt auf dem Handle.

    Sichten des Programmierers

    • Reactor Pattern:
      reactor.jpg

    Anwendungsentwickler

    Ich will netzwerktransparent wissen, wie lange die Design Pattern Runde noch dauert? Oder ein bißchen formaler:
    Implementiere einen Server, der auf eine Browser Anfrage ( HTTP-GET ) ein html Seite schickt, die die verbleibende Zeit bis zum Ende der Design Pattern Runde darstellt. Noch formaler
    Client macht eine HTTP-GET Request right Server nimmt Request an und dispatcht sie auf den Event Handler right Event Handler schickt die Antwort zum Client Dazu müssen drei Schritte als Anwendungsentwickler und Nutzer der Reaktor Struktur implementiert werden.
    Ich verwende den BaseHTTPServervon Python.

    Request Handler implementieren

    class RequestHandler(BaseHTTPRequestHandler):


    def do_GET(self):
    import datetime
    actTime= datetime.datetime.now()

    endTime= datetime.datetime( 2007,5,22,9)

    diffSeconds= (endTime-actTime).seconds
    self.send_response(200)
    self.send_header('Content-type', 'text/html')

    self.end_headers()
    self.wfile.write("""<?xml version="1.0" ?>

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>

    <title>Wie lange dauerts noch?</title>
    <script src="https://scwiki.science-computing.de/twiki/pub/TWiki/ChecklistPlugin/itemstatechange.js"
    language="javascript" type="text/javascript"></script></head>

    <body>

    <h1 align="center"><font size="10" color="#FF0000">Count down</font></h1>

    <p align="center"> <font size="6"> %s Sekunden noch bis zum Ende der Design Pattern Runde.</font> </p>

    </body>
    </html>""" %(str(diffSeconds)))

    Request Handler registrieren

    srvr = HTTPServer(("",4711),RequestHandler)

    Event loop laufen starten

    srvr.serve_forever()  

    Frameworkentwickler

    Ich will ein Reaktor Framework in Python entwickeln, das auf Input Events reagiert. Exemplarisch werden vier verschiedene Typen von Input Events gleichzeitig prozessiert und die entsprechenden Nachrichten in ähnlichnamige Dateien ins home geschrieben.
    1. stdin
      • jede stdin Eingabe erzeugt ein Event, das zur Prozessierung des Events führt
    2. lesen einer Datei
      • nach dem Einlesen der Datei wird der Handler wieder entfernt
    3. lesen einer url
      • nach dem Einlesen der Internetressource wird der Handler wieder entfernt
    4. HTTP - GET Anfrage
      • bei jedem stdin Event will ich wissen, wie lange die Design Pattern Runde noch dauert; dazu müssen periodisch folgende Schritte vollzogen werden
        • registriere den Event Handler, um den ReactorPattern Server zu fragen
        • darstellen des Ergebnisses auf stderr
        • deregistrieren des Event Handlers
    ##################
    #Event Handlers
    ##################

    import os
    import socket
    import sys
    import time


    class EventHandler:

    def handle_input(self,device):
    raise NotImplementedError

    def handle_output(self,device):
    raise NotImplementedError

    def handle_exception( self,device):
    raise NotImplmentedError

    def getHandle( self ):
    return NotImplementedError

    class InputEventHandler( EventHandler ):

    def __init__(self,device,dest):

    self.handleDevice= device
    self.outFile=open(dest,"a")

    firstLine= "Read from %s with handle %s \n" % ( device.name , device.fileno() )

    self.outFile.writelines([firstLine,"\n"])


    def handle_input(self,device):


    inp= device.readline().strip()
    self.outFile.write( inp + "\n" )

    self.outFile.flush()
    myReactor.registerHandler( InputToStderrEventHandler( urllib.urlopen("http://ackbar:4711"),

    os.getenv("HOME")+"/test.WieLangNoch" ) , "r")

    def getHandle( self ): return self.handleDevice

    class FileInputEventHandler( InputEventHandler ):

    def __init__(self,device,dest):

    self.handleDevice= device
    self.outFile=open(dest,"a")

    name=""
    try:
    name= device.name
    except:

    name= device.geturl()

    firstLine= "Read from %s with handle %s \n" % ( name , device.fileno() )

    self.outFile.writelines([firstLine,"\n"])


    def handle_input( self, device ):

    for line in device.readlines():
    self.outFile.write( line.strip() + "\n" )

    self.outFile.flush()
    Reactor().removeHandler( self ,"r" )

    class InputToStderrEventHandler( FileInputEventHandler ):

    def handle_input( self, device ):

    for line in device.readlines():
    self.outFile.write( line.strip() + "\n" )

    if line.startswith("<p align"):
    message= line.split(">")[2].split("<")[0]

    sys.stderr.write( message )
    self.outFile.flush()

    Reactor().removeHandler( self,"r")





    #############

    # Reactor
    #############

    class Singleton(object):
    def __new__(cls, *args, **kwds):

    it = cls.__dict__.get("__it__")
    if it is not None:

    return it
    cls.__it__ = it = object.__new__(cls)

    it.init(*args, **kwds)
    return it
    def init(self, *args, **kwds):

    pass



    import select

    class Reactor( Singleton ):

    readHandles={}
    writeHandles={}
    exceptHandles={}


    def registerHandler( self, eventHandler,eventTypes):

    handle= eventHandler.getHandle()
    handleId= handle.fileno()


    if "r" in eventTypes: Reactor.readHandles[handleId]= (handle,eventHandler)

    if "w" in eventTypes: Reactor.Reactor.writeHandles[handleId]= (handle,eventHandler)

    if "e" in eventTypes: Reactor.Reactor.exceptHandles[handleId]= (handle,eventHandler)


    def removeHandler( self, eventHandler ,eventTypes ):

    handle= eventHandler.getHandle()
    handleId= handle.fileno()


    if "r" in eventTypes: del Reactor.readHandles[handleId]

    if "w" in eventTypes: del Reactor.writeHandles[handleId]

    if "e" in eventTypes: del Reactor.exceptHandles[handleId]


    def handleEvents( self):

    while ( 1 ):

    rHandle, wHandle,eHandle= select.select( Reactor.readHandles.keys(), Reactor.writeHandles.keys(),
    Reactor.exceptHandles.keys() )

    print "all ready handle: Reactor.readHandles: %s Reactor.writeHandles: %s Reactor.exceptHandles: %s "
    %(rHandle,wHandle,eHandle)

    for han in rHandle:
    handleDevice= Reactor.readHandles[han][0]

    eventHandler= Reactor.readHandles[han][1]
    eventHandler.handle_input( handleDevice )

    for han in wHandle:
    handleDevice= Reactor.writeHandles[han][0]

    eventHandler= Reactor.writeHandles[han][1]
    eventHandler.handle_output( handleDevice )


    for han in eHandle:
    handleDevice= Reactor.exceptHandles[han][0]

    eventHandler= Reactor.exceptHandles[han][1]
    eventHandler.handle_exception( handleDevice )

    print "Reactor:handleEvents: waiting for input "

    import urllib

    myReactor=Reactor()
    myReactor.registerHandler( InputEventHandler( sys.stdin ,
    os.getenv("HOME")+"/test.stdin" ) ,"r")

    myReactor.registerHandler( FileInputEventHandler( open("/etc/services"),
    os.getenv("HOME")+"/test.services" ) ,"r")

    myReactor.registerHandler( FileInputEventHandler( urllib.urlopen("http://www.heise.de"),
    os.getenv("HOME")+"/test.heise" ) , "r")

    myReactor.handleEvents()

    Aspekte des Reaktor Frameworks

    Event Handler Interface festlegen
    class EventHandler:        

    def handle_input(self,device):
    raise NotImplementedError

    def handle_output(self,device):
    raise NotImplementedError

    def handle_exception( self,device):
    raise NotImplmentedError

    def getHandle( self ):
    return NotImplementedError
    Reaktor implementieren
    import select

    class Reactor( Singleton ):

    readHandles={}
    writeHandles={}
    exceptHandles={}


    def registerHandler( self, eventHandler,eventTypes):

    handle= eventHandler.getHandle()
    handleId= handle.fileno()


    if "r" in eventTypes: Reactor.readHandles[handleId]= (handle,eventHandler)

    if "w" in eventTypes: Reactor.Reactor.writeHandles[handleId]= (handle,eventHandler)

    if "e" in eventTypes: Reactor.Reactor.exceptHandles[handleId]= (handle,eventHandler)


    def removeHandler( self, eventHandler ,eventTypes ):

    handle= eventHandler.getHandle()
    handleId= handle.fileno()


    if "r" in eventTypes: del Reactor.readHandles[handleId]

    if "w" in eventTypes: del Reactor.writeHandles[handleId]

    if "e" in eventTypes: del Reactor.exceptHandles[handleId]


    def handleEvents( self):

    while ( 1 ):

    rHandle, wHandle,eHandle= select.select( Reactor.readHandles.keys(), Reactor.writeHandles.keys(),
    Reactor.exceptHandles.keys() )

    print "all ready handle: Reactor.readHandles: %s Reactor.writeHandles: %s Reactor.exceptHandles: %s "
    %(rHandle,wHandle,eHandle)

    for han in rHandle:
    handleDevice= Reactor.readHandles[han][0]

    eventHandler= Reactor.readHandles[han][1]
    eventHandler.handle_input( handleDevice )

    for han in wHandle:
    handleDevice= Reactor.writeHandles[han][0]

    eventHandler= Reactor.writeHandles[han][1]
    eventHandler.handle_output( handleDevice )


    for han in eHandle:
    handleDevice= Reactor.exceptHandles[han][0]

    eventHandler= Reactor.exceptHandles[han][1]
    eventHandler.handle_exception( handleDevice )

    print "Reactor:handleEvents: waiting for input "
    Um ein Eventhandler zu registrieren wird zusätzlich der Event Typ benötigt, für den sich der Event Handler interessiert.
    Durch die Registratur des Eventhandler ist es möglich, über den Filehandle ( z.B.: stdin = 0 ) sowohl das Fileobject, die Eventsource und den Eventhandler, die Anwendungslogik zu erhalten.
    In handle Events bedient sich der Reaktor dem nativen select Befehl um auf relevante Events registrieren zu können. handleEvents stellt die Eventloop des Reaktor dar, die, einmal gestartet, immer auf eingehende Events lauscht.

    Ausgabe, abhängig von den registrierten Event Handles

    • stdin wird registriert
      python reactorInput.py
      4
      all ready handle: Reactor.readHandles: [0] Reactor.writeHandles: [] Reactor.exceptHandles: []
      Reactor:handleEvents: waiting for input
    Erst durch die Eingabe der Zahl 4 wird die Eventloop prozessiert. Ein Eingabe Event Reactor.readHandles: [0]liegt nun vor.
    • stdin, Datei und Url Request werden registriert
      python reactorInput.py
      all ready handle: Reactor.readHandles: [4, 6] Reactor.writeHandles: [] Reactor.exceptHandles: []
      Reactor:handleEvents: waiting for input
      4
      all ready handle: Reactor.readHandles: [0] Reactor.writeHandles: [] Reactor.exceptHandles: []
      Reactor:handleEvents: waiting for input
    Die Ressourcen file und url sind beim Starten der Eventloop registriert, daher werden sie sofort prozessiert all ready handle: Reactor.readHandles: [4, 6] Reactor.writeHandles: [] Reactor.exceptHandles: [] . Da ich sie explizit deregistriere sind sie bei in dem Eintreten eines stdin-Events nicht mehr vorhanden all ready handle: Reactor.readHandles: [0] Reactor.writeHandles: [] Reactor.exceptHandles: []. Der Reaktor prozessiert nun nur noch den Filedescriptor 0, also stdin.
    • stdin, Datei , Url und HTTP-GET Request werden registriert
      all ready handle: Reactor.readHandles: [4, 6] Reactor.writeHandles: [] Reactor.exceptHandles: []
      Reactor:handleEvents: waiting for input
      4
      all ready handle: Reactor.readHandles: [0] Reactor.writeHandles: [] Reactor.exceptHandles: []
      Reactor:handleEvents: waiting for input
      all ready handle: Reactor.readHandles: [4] Reactor.writeHandles: [] Reactor.exceptHandles: []
      49661 Sekunden noch bis zum Ende der Design Pattern Runde.Reactor:handleEvents: waiting for input
    Die stdin Abfrage registriet nun einen neuen Eventhandler, der die mir die Frage beantwortert: Wie lange dauert noch die Design Pattern Runde? Dieser Request erhält wieder den Filedescriptor 4. all ready handle: Reactor.readHandles: [4] Reactor.writeHandles: [] Reactor.exceptHandles: []

    Implementierung

    Die Implementierung des Reaktor Patterns lässt sich in zwei Schichten unterteilen. Die Frameworkschicht, die die applikationsunabhängige demultiplex/dispatch Infrastruktur zur Verfügung stellt und die Applikationschicht, die die konkreten Eventhandler liefert. In der klassischen, einfachsten Form, geschieht das ganz Eventhandling in einem Prozeß.

     Definiere das Event Handler Interfaces

    Die Methoden des Event Handlers legen das Servcie-Interface des Reaktor Frameworks fest.
    1. bestimme den Typ des dispatchingZiels
      • verwende eine Event Handler Objekt oder eine Event Handle Funktion
    2. bestimme die Event Handling dispatching Strategie
      1. dispatch auf eine einzelne Methode
        ...
        virtual void handlle_event( Handle handle, Event_Type et)= 0;
        ...
      2. dispatch auf mehrere Methoden
        ...
        virtual void handle_input( Handle handle )=0;
        virtual void handle_output( Handle handle )=0;
        virtual void handle_timeout( Handle handle )=0;
        ...
      • das eine Methode Interface erlaubt es einfach, das Framework um neue Eventtypen zu erweitern
      • während bei handle_event die ganze Verteilungsstrategie mittels Bedingungen auf Applikationsebene definiert werden muß, geschieht der dispatch auf dem reichhaltigeren Interface automatisch auf Frameworkebene
      • insbesondere ist bei feingranularen Dispatch möglich, spezielle hook Methoden in konkreten Event Handlern zu überschreiben

     Definiere das Reaktor Interface

    Die Applikation nutzt einerseits das Reaktor Interface um die spezifischen Event Handler zu de/registrieren und andererseits die Event Loop zu starten. Gerne wird das Reaktor Interface als Singleton implementiert, das die Anfragen an die Reaktor Implementierung delegiert. Neben dem Event Handler erhält erhält die register_handler als zweites Argument den Event Type als Argument, für den sie sich interessiert.
    void Select_Reactor_Implementation::register_handler( EventHandler* eventHandler, Event_Type event_type )

     Implementiere das Reaktor Interface

    • entkopple das Reaktor Interface von seiner Implementierung durch eine Brücke right mehrere verschiedene Implementierung können unterstützt werden ( select, poll, WaitFormMultipleObjects , GuiEventLoops ,... )
    • wähle einen synchronen event demutliplexer aus
    int select( u_int max_handle_plus_1 , 
    fd_set *read_fds, fd_set * write_fds, fd_set *except_fds,

    timeval *timeout);
    • implementiere ein demultiplexing table
      • ein Eintrag soll von der Form < handle, event_handle, indication event type > sein, wobei handle als Schlüssel für den Event Handler bei einem indication event ( connect, expire, read,... ) verwendet wird
    • definiere die Reaktor Implementierung
    # select Server( only demultiplexing ) 
    def get_request(self):

    while self._running:
    log.info('select on listen socket')
    # demultiplex

    rs, ws, es = select.select([self.socket], [], [], 10)

    if rs:
    log.info('accepting new connection')
    # socketobject and address
    return self.socket.accept()
    log.info('ending request loop')

    return (None, None)

     Bestimme die Anzahl der Reaktoren, die man benötigt

    • in der Regel sollte der Reaktor ein Singleton sein, jedoch erlaubt win32 nur 64 Handles pro Reaktor
    • aus Echtzeitforderungen kann es nötig sein mehrere Reaktoren gleichzeitig laufen zu lassen; Trennung der Reaktoren nach Eventtypen

     Implementiere die konkreten Eventhandler

    • sie stellen die Anwendungslogik dar, im Gegensatz zu dem bisher vorgestellten Reaktor-Framework
    • statte die Eventhandler gegebenfalls mit einem Zustand aus; vgl. Beispiel Timer mit ACE
    • implementiere die Eventhandler Funktionalität

    Kritik

    • Vorteile
      • klare Trennung von Framework- und Applikationslogik
      • Modularität von eventgetriebenen Anwendungen durch verschieden Eventhandler
      • Portabilität durch Trennung von Interface und Implementierung des Reaktors
      • einfache Parallelität durch den synchronen event demutliplexers
    • Nachteile
      • setzt einen event demultiplexer voraus
      • Durchsatzprobleme bei lang laufenden Event Handler in single Threaded Applikation, den der Event Handler blockiert den Reaktor
      • schwierig zu debuggen und zu testen durch die inversion of control

    Verwendete Patterns - Techniken

    • ObserverPattern
      • der Event Handler wird informiert, sobald ein für ihn spezifisches Event auftritt
    • BridgePattern
      • der Reaktor hält sich eine spezifische Reaktor Implementierung, an die er die Aufrufe delegiert
    • TemplateMethodePattern
      • die handle_* Methoden als hook Methoden werden wohl in statisch typisierten Programmiersprachen in einer definierten Reihenfolge prozessiert
    • double Dispatch: registerHandler right getHandle

     

  •  

    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 700

Gestern 3357

Woche 12263

Monat 39580

Insgesamt 3892294

Aktuell sind 33 Gäste und keine Mitglieder online

Kubik-Rubik Joomla! Extensions

Abonniere den Newsletter (+ pdf Päckchen)

Beiträge-Archiv

Sourcecode

Neuste Kommentare