Donnerstag, 5. Februar 2009

Einführung in Entwurfsmuster (Teil 5) / Introduction to design patterns (Part 5)

Im Dezember 2006/Januar 2007 schrieb Andy Kramek in seinem Blog über die Implementierung von Entwurfsmustern in Visual Foxpro. Andy hat mir erlaubt, meine deutsche Übersetzung seiner Artikelserie hier zu veröffentlichen. An dieser Stelle für seine Erlaubnis nochmals herzlichen Dank.

Heute nun Teil 5, der sich mit dem Dekorierer beschäftigt.

Hinweis: Die Überschriften der jeweiligen Kapitel sind direkt mit Andy's Originalbeitrag verknüpft.

Einführung in Entwurfsmuster (Design Patterns)
Von Andy Kramek

Der Dekorierer (The Decorator)

Der Dekorierer beschreibt eine Lösung zu dem Problem, ein Objekt in seiner Funktionalität zu erweitern, ohne den eigentlichen Code des Objektes zu verändern.

Wie erkenne ich einen Dekorierer?

Die formale Definition des Dekorierers gemäß „GoF“ lautet:
Erweitere ein Objekt dynamisch um Zuständigkeiten.
Der Bedarf, Funktionalitäten (oder Verhalten) eines Objektes dynamisch zu ändern tritt in zwei Situationen auf. Erstens, wenn der Quellcode eines Objektes schlicht nicht verfügbar ist, vielleicht handelt es sich um ein ActiveX-Control oder es kommt eine Klassenbibliothek eines Drittanbieters zum Einsatz. Zweitens, wenn die Klasse intensiv genutzt wird, die spezifische Verantwortlichkeit jedoch nur in ein oder zwei bestimmten Situationen benötigt wird und es nicht angebracht wäre, den Code der Klasse einfach hinzuzufügen.

In den vorangegangenen Beiträgen haben wir verschiedene Wege zu Feststellung der Steueraufschläge in Abhängigkeit der Lokation betrachtet. Das grundsätzliche Steuerberechnungsobjekt arbeitet in jedem Fall mit einer sichtbaren Methode namens CalcTax() welche zwei Parameter erwartet (einen Wert und eine Rate), und gibt den fälligen Steuerbetrag zurück. Wir bekamen das Grundproblem bezüglich des Umgangs mit den Steuern mit Hilfe des Brückenmusters in den Griff, indem wir die Implementierung von der Schnittstelle separierten. Obwohl es flexibler war als eine einfache direkte Codierung, stellten wir jedoch sehr schnell fest, dass es nicht flexibel genug war, um mit unterschiedlichen Steuerraten in unterschiedlichen Lokationen zurechtzukommen.

Sowohl die Strategie als auch die Zuständigkeitskette kommen mit dieser Anforderung zurecht, allerdings bedeuten beide Lösungswege das Klassenableitungen eingesetzt werden müssen. Das Dekorierer-Muster erlaubt es uns, dieses Problem ohne Ableitungen der Originalklasse zu lösen.

Stattdessen definieren wir ein neues Objekt mit gleichem Interface wie der Steuerrechner, welches jedoch den notwendigen Code beinhaltet, um die passende Steuerrate der gegebenen Lokation festzustellen. Dieses Objekt sieht genauso aus wie der Steuerrechner, es kann jedoch, weil es über eine Referenz auf den echten Steuerrechner verfügt, jedwede Berechnungsanfrage vorverarbeiten und anschließend einfach die echte Implementierung aufrufen.

Was sind die Komponenten eines Dekorierer?

Ein Dekorierer stellt zwei grundsätzlich Anforderungen. Zunächst einmal benötigen wir die Implementierungsklasse, welche die Schnittstelle und die grundlegende Funktionalität definiert. Desweiteren benötigen wir einen Dekorierer der die Schnittstelle der Implementierung reproduziert und über eine Referenz auf diese verfügt. Das Klient-Objekt leitet nun Aufrufe die eigentlich direkt an die Implementierung gegangen wären, an das Dekorierer Objekt. Die Basisstruktur für das Dekorierer-Muster sieht wie folgt aus:

 
Eigentlich ist dieses Muster eine erweiterte Brücke. Soweit es den Klienten betrifft kann er den Dekorierer aufrufen, als ob es sich um die Implementierung am Ende einer Standardbrücke handelt, da beide Schnittstellen identisch sind. Soweit es die Implementierung betrifft sieht der Aufruf genauso aus, wie wenn er direkt vom Klienten gekommen wäre. Letztlich muss sie nicht einmal wissen, dass es den Dekorierer überhaupt gibt.

Wie implementiere ich einen Dekorierer?

Eine neue Klasse mit dem Namen ‚cntDekorator‘ wird definiert um das Dekorierer-Beispiel zu implementieren. Es hat zwei spezielle Eigenschaften und eine spezielle Methode wie im folgenden beschrieben:

Name          Beschreibung
cImpClass     Name der für diesen Dekorierer zu instanziierenden Klasse
cImpLib       Klassenbibliothek der zu instanziierenden Implementierungsklasse
oCalculator   Objektreferenz auf eine Instanz der real implementierten Klasse
              der CalcTax() Methode
              Dieses Objekt wird innerhalb der INIT() Methode des Dekorierers
              instanziiert.
CalcTax          Die Implementierung des Dekorierers für die äquivalente Methode
              der realen Implementierung.


Eigentlich ist die Dekorierer-Klasse sehr einfach aufgebaut. Wenn sie erzeugt wird instanziiert sie die reale Implementierungsklasse, welche über die Namens- und Bibliotheks-Eigenschaften definiert ist. Zusätzlich implementiert sie eine operationale Methode (in diesem Fall ‚CalcTax‘), welche genauso benahmt ist und über dieselbe Signatur wie die reale Methode verfügt.

Der Code in der CalcTax()-Methode des Dekorierer ist ziemlich gradlinig. Er erwartet zwei Parameter, die LokationsID als String und den Preis für den die Steuer berechnet werden soll. Beachten Sie, dass dies nicht dieselben Parameter sind, die von der CalcTax() Methode der realen Klasse benötigt werden (dort übergeben wir Preis und Steuerrate). Die CalcTax() Methode des Dekorierers stellt die passende Rate basierend auf der LokationsID fest und ruft dann die CalcTax() Methode des Implementierungsobjekts mit den realen Parameters auf. Der Rückgabewert wird unverändert einfach nur an den Klienten durchgereicht.

LPARAMETERS tcLocation, tnPrice

LOCAL lnRate, lnTax
STORE 0 TO lnRate, lnTax
*** Korrekte Steuerrate feststellen
DO CASE
CASE tcLocation = [01]
    lnRate = 5.75
CASE tcLocation = [02]
    lnRate = 5.25
CASE tcLocation = [03]
    lnRate = 0.00
OTHERWISE
    lnRate = 0
ENDCASE

*** Nun die reale Implementierung aufrufen
lnTax = This.oCalculator.CalcTax( tnPrice, lnRate )

*** Und das Ergebnis zurückgeben
RETURN lnTax

Um den Dekorierer nutzen zu können müssen wir nur eine kleine Änderung im Klienten durchführen. Anstatt die reale Implementierung zu instanziieren muss nun das Dekorierer Objekt instanziiert werden. Denken Sie daran, dass die Aufrufsignatur des Dekorierers mit der realen Methode identisch ist. Somit müssen keine weiteren Änderungen durchgeführt werden.

LPARAMETERS tcContext, tnPrice
LOCAL lnTax
WITH ThisForm
    *** Pruefen, ob der Dekorierer verfuegbar ist
    IF VARTYPE( This.oCalc ) # [O]
        *** Ist er nicht, also erzeugen wir ihn
        .oCalc = NEWOBJECT( [cntDecorator], [calclass.vcx] )
    ENDIF
    *** Nun einfach die CalcTax() Methode aufrufen und Lokation und Preis uebergeben
    lnTax = .oCalc.CalcTax( tcContext, tnPrice )
    IF ISNULL( lnTax )
        *** Die Daten konnten nicht verarbeitet werden
        MESSAGEBOX( [Unable to Process this Location], 16, [Failed] )
        lnTax = 0
    ENDIF
    RETURN lnTax
ENDWITH

Obwohl es sich hier um ein sehr einfaches Beispiel handelt, können Sie sehen, wie leicht es ist, die Funktionalität mit Hilfe eines Dekorierers zu erweitern. Eine typisches ‚real life‘ Problem, das mit Hilfe dieses Musters angegangen werden kann ist, wie man implementierungsspezifische Validierungen bei generischen Speicherroutinen unterstützt.

Dekorierer Zusammenfassung

Das Dekorierer Muster kommt zum Einsatz, wenn wir das grundsätzliche Verhalten einer spezifischen Instanz modifizieren möchten, ohne dabei entweder eine Klassenableitung zu erzeugen, oder den Originalcode zu ändern. Letztlich agiert der Dekorierer als Prä-Prozessor der Implementierung wobei er sich zwischen Klient und Implementierung schaltet.

Quellennachweis:

Entwurfsmuster - Elemente wiederverwendbarer objektorientierter Software ADDISON-WESLEY ISBN 3-89319-950-0

Keine Kommentare:

Kommentar veröffentlichen