Donnerstag, 5. März 2009

Neuigkeiten über VFPx

In den letzten vier Wochen gab es wieder jede Menge neues im Bereich VFPx.

Bereich Abgeschlossene Projekte
  • Seit dem 22. Februar steht die Version 3.01 des PEM Editors zur Verfügung.

Bereich Release Candidates
  • Seit dem 18. Februar die von Rick Schummer angekündigte korrigierte und erweiterte VFP9 SP2 Hilfedatei in der Version 1.05 bereit.
  • Seit dem 9. Februar gibt es die Version 3.5.0 der Themed Controls.

Bereich Beta Releases
  • Seit dem 28. Februar gibt es die neue Version 00.09 der FoxTabs.

In meinem Blogeintrag vom 26. Februar 2008 -VFPx auf Codeplex aktualisiert- pflege ich die Neuerungen kontinuierlich nach. Die eigentlichen Projekte können von dort direkt aufgerufen werden.

Auch wenn wir uns derzeit in der Fastenzeit befinden kann ich nur sagen: "Ran an den Speck!"

Donnerstag, 12. Februar 2009

Nostalgie als VFP-Startprogramm

Vor ein paar Tagen fiel mir beim Durchstöbern eines alten VFP-Verzeichnisses ein kleines Programm auf, dass den C64 Bootscreen simuliert. Nun war der alte Brotkasten nicht wirklich mein erster Computer (mein erster Commodore, ein C116, war eher ein überdimensionierter Taschenrechner dessen Abmessungen die der zum laden und speichern der Programme benötigten Datasette nur knapp überstieg und bald durch einem C128 ersetzt wurde). Das hinderte mich jedoch nicht, das kleine prg ein klein wenig umzustellen und als Startprogramm innerhalb von VFP/Extras/Optionen/Dateiablage/Startprogramm zu hinterlegen. Wenn ich nun VFP starte werde ich wieder vom blauen Bootscreen begrüßt...

WITH _screen
.BackColor = RGB(1,0,162)
.ForeColor = RGB(83,81,240)
.FontSize = 24

CLEAR
DECLARE Sleep IN WIN32API INTEGER
LOCAL liWait as Integer
liWait = 500

Sleep(liWait)
? [ **** COMMODORE 64 ROM V1.1 ***]
Sleep(liWait)
?
Sleep(liWait)
? [ 64K RAM SYSTEM 38911 BYTES FREE]
?
Sleep(liWait)
? [READY.]
Sleep(liWait)
? [LOAD"VISUAL FOXPRO 9",8,1]
?
Sleep(liWait)
? [SEARCHING FOR VISUAL FOXPRO 9]
Sleep(liWait)
? [LOADING ]
FOR i = 1 TO 5
Sleep(liWait)
??[*]
ENDFOR
Sleep(liWait)

.BackColor = RGB(255,255,255)
.ForeColor = RGB(0,0,0)
CLEAR
?? [VISUAL FOXPRO 9 ...READY AND WAITING FOR INPUT]

.FontSize = 9
ENDWITH

Donnerstag, 5. Februar 2009

Einführung in Entwurfsmuster (Teil 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.

Freitag, 23. Januar 2009

Reparierte VFP9 SP2 Hilfedatei wird unter VFPX bereitgestellt

Gute Nachricht von Rick Schummer zur im letzten Jahr im April erschienenen defekten SP2 Hilfedatei.

Nachdem diverse (noch nicht genannte) Entwickler in den letzten Monaten die Hilfedatei durch Decompilieren, Reparieren und Neukompilieren von ihren ursprünglichen Problemen befreit haben, hat YAG von Microsoft Seite nun die Genehmigung erteilt, die Hilfedatei auf VFPX unter der Creative Common License zu veröffentlichen. Diese ermächtigt die VFP Community Verbesserungen an der Hilfedatei vorzunehmen.

Rick kündigt an, dass sobald die letzten Fehler bereinigt sind, die Datei auf VFPX bereitsteht.

Donnerstag, 15. Januar 2009

WAV-Sounds mit VFP abspielen

Vor einiger Zeit hatte ich mir einen kleiner TeaTimer gebaut, damit mein schwarzer Tee nicht dauernd 10 bis 20 Minuten zieht. Das Ganze passte auch prima, solange ich die Timer-Form im Auge behielt. Dummerweise schaue ich während der Arbeit mit Foxpro aber auf mein Codefenster und so passierte es recht häufig, dass ich doch wieder einen 20 Minuten Tee da stehen hatte. Lange Rede kurzer Sinn, es musste etwas 'ohrenfälligeres' her, denn auch das Flackern des Timers bei abgelaufener Zeit half nicht wirklich.

Nachdem also der Entschluss zum Abspielen einer WAV-Datei gefallen war, musste ich feststellen, dass mir das Beispiel in den Solution Samples nicht auf Anhieb die Informationen preisgab, die mich interessierten. Irgendwo im Web fand ich dann ein Stück Mustercode zum Aufrufen der MCI API-DLL, welches ich als Grundlage für den u.a. Beispielcode verwendet habe. Seitdem funktionieren auch meine 3 und 4 Minuten Tees... ;-)

Die notwendige API befindet sich im Windows-System32 Verzeichnis und heisst WinMM.dll. Sie beinhaltet das Media Control Interface aus dem letztlich nur die Funktion PlaySound deklariert werden muss.

Die u.a. Funktion PlayWAV kann mit oder ohne Parameter aufgerufen werden. Wird der Name einer WAV-Datei übergeben, so wird versucht, diese abzuspielen. Andernfalls wird eine Standarddatei aus dem Windows Media Verzeichnis abgespielt. Die möglichen Werte des zweiten Parameters können dem u.a. DEFINE-Block entnommen werden.
?PlayWAV()

FUNCTION PlayWAV

LPARAMETERS vWavFile as String, vFlags as Integer

LOCAL llReturn as Boolean
llReturn = .T.

* // -----------------------------------------------------------
* // vWavFile = (optional)
* // Pfad und Dateiname der abzuspielen WAV-Datei
* //
* // vFlag = (optional)
* // gezielte Abspielvariante gem. u.a. DEFINEs
* // -----------------------------------------------------------

#DEFINE SND_SYNC 0x00000000 && synchron abspielen (default)
#DEFINE SND_ASYNC 0x00000001 && asynchron abspielen
#DEFINE SND_NODEFAULT 0x00000002 && silence (!default) wenn WAV nicht gefunden
#DEFINE SND_MEMORY 0x00000004 && zeigt auf eine im RAM befindliche Datei
#DEFINE SND_LOOP 0x00000008 && solange abspielen bis neue WAV übergeben wird
#DEFINE SND_NOSTOP 0x00000010 && derzeit ablaufende Sounds nicht unterbrechen
#DEFINE SND_NOWAIT 0x00002000 && nicht warten wenn der Treiber 'busy' ist
#DEFINE SND_ALIAS 0x00010000 && Name ist ein registrierter ALIAS
#DEFINE SND_ALIAS_ID 0x00110000 && Alias ist eine vordefinierte ID
#DEFINE SND_FILENAME 0x00020000 && Name ist ein Dateiname
#DEFINE SND_RESOURCE 0x00040004 && Name ist eine Ressource oder Atom
#DEFINE SND_PURGE 0x00000040 && entferne zum Abspielen nicht-statische Tasks
#DEFINE SND_APPLICATION 0x00000080 && Suche nach assoziierter Applikation

* // WinMM.dll -> MCI API-DLL -> MCI = Media Control Interface
TRY
DECLARE INTEGER PlaySound IN WinMM.dll AS PLAYSOUNDOVERAPI ;
STRING @ pszSound, INTEGER HMODULE_hmod, LONG DWORD_fdwSound

m.vWavFile = IIF( ;
VARTYPE(m.vWavFile) <> [C] OR !FILE(m.vWavFile), ;
ADDBS(GETENV([windir])) + [media\tada.wav], ;
m.vWavFile ;
)
m.vFlags = IIF( ;
VARTYPE(m.vFlags)=[N], ;
m.vFlags, ;
SND_ASYNC+SND_FILENAME ;
)
PLAYSOUNDOVERAPI(m.vWavFile,0,m.vFlags)
CATCH
* // API Aufruf schlug fehl, also nix mit TADA
llReturn = .F.
ENDTRY

RETURN llReturn

ENDFUNC

Montag, 12. Januar 2009

Einführung in Entwurfsmuster (Teil4)

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 4, der sich mit der Zuständigkeitskette 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

Die Zuständigkeitskette (Chain of Responsibility)

Was ist eine Zuständigkeitskette und wie setze ich sie ein?

Im vorangegangenen Kapitel wurde mit dem Strategiemuster eine Lösung aufgezeigt wie mit verschiedenen alternativen Implementierungen zur Laufzeit umgegangen werden kann. Die Zuständigkeitskette beschreibt ein alternatives Muster das dieses Problem von einer anderen Seite aus beleuchtet.

Wie erkenne ich, wann ich eine Zuständigkeitskette benötige?

Die formale Definition der Zuständigkeitskette gemäß „GoF“ lautet:

Vermeide die Kopplung des Auslösers einer Anfrage mit seinem Empfänger, indem mehr als ein Objekt die Möglichkeit enthält, die Aufgabe zu erledigen. Verkette die empfangenden Objekte und leite die Anfrage an der Kette entlang, bis ein Objekt sie erledigt.

Im vorangegangenen Beispiel habe ich aufgezeigt, wie eine Strategie eingesetzt wird, um ortsspezifische Mehrwertsteuersätze zu verarbeiten. Hierbei haben wir jedoch gesehen, dass zur Implementierung einer Strategie irgendein Objekt zur Laufzeit entscheiden muss, welche der möglichen Unterklassen implementiert werden soll. Dies dürfte nicht immer erwünscht oder gar möglich sein.

Innerhalb einer Zuständigkeitskette kann jedes Objekt selbst beurteilen, wie es eine Anfrage zu verarbeiten hat und, sollte es die Anfrage nicht selbst verarbeiten können, weiß es nur, wie diese an ein anderes Objekt weitergereicht wird, und somit haben wir eine Kette. Die Konsequenz daraus ist, dass der Klient (welcher die Aktionsanfrage initiiert) somit nur noch das erste Objekt innerhalb der Kette kennen muss. Darüber hinaus muss jedes Objekt innerhalb der Kette nur das nächste Glied der Kette kennen. Die Zuständigkeitskette kann sowohl als vordefinierte (statisch) oder dynamische Kette (zur Laufzeit erzeugt jedes Objekt im Bedarfsfall seinen eigenen Nachfolger) implementiert werden.

Welches sind die Komponenten einer Zuständigkeitskette?

Eine Zuständigkeitskette kann durch die Erzeugung einer abstrakten Steuerungsklasse (‚Handler‘) implementiert werden, welche das Interface und die generischen Funktionalitäten spezifiziert, und durch anschließendes Erzeugen der konkreten Unterklassen um die diversen möglichen Implementierungen zu definieren. Es ist jedoch keinesfalls eine starre Vorgabe für die Definition aller Glieder einer Zuständigkeitskette, dass diese von derselben Klasse abgeleitet sein müssen um sicherzustellen, dass sie alle das über notwendige Interface zu Integration anderer Kettenglieder verfügen.

Klienten Objekte benötigen eine Referenz auf die spezifische Unterklasse, welche ihren individuellen Einstiegspunkt in die Kette darstellt. Dies bedeutet jedoch wiederum nicht, dass alle Klienten denselben Einstiegspunkt benutzen müssen.


Und wieder können wird das grundsätzliche Brückenmuster erkennen. Dies liegt daran, dass jede Verknüpfung innerhalb der Kette letztlich eine Brücke zwischen einer Abstraktion und einer Implementierung darstellt. Der einzige Unterschied zur simplen Brücke liegt darin, dass jedes einzelne Objekt in Abhängigkeit seiner jeweiligen Situation, beide Rollen übernehmen kann.

Wie implementiere ich eine Zuständigkeitskette?

Die Frage, die ich im Zusammenhang mit dem Strategiemuster gestellt habe war „Wie gehe ich die Berechnung der Umsatzsteuer an, wenn die Berechnung vom Verkaufsort abhängt“. Sie werden sich erinnern, dass die Lösung so aussah, dass spezielle Unterklassen definiert wurden um jede Steuerrate gezielt verarbeiten zu können. Im Anschluss wurde dann auf Basis einer Tabelle entschieden, welche Unterklasse für welchen Ort benötigt wird.

Um dasselbe Problem mit einer Zuständigkeitskette zu lösen können wir die ursprüngliche Steuerberechnungsklasse heranziehen, die wir für die Strategie definiert haben und fügen die folgenden Eigenschaften hinzu:

cCanHandle  Definiert den Kontext der mit dieser Klasse verarbeitet werden kann
cNextObj Name des als nächstes zu instanziierenden Objektes in der Kette
cNextObjLib Klassenbibliothek des nächsten Objektes in der Kette
oNext Objektreferenz des nächsten Objektes in der Kette

Die Eigenschaft ‚cCanHandle‘ definiert den ‚Kontext‘ den die spezielle Instanz akzeptiert, wohingegen die ‚Next‘-Eigenschaften genutzt werden, um das nachfolgende Objekt zu definieren. Dieses könnte zum einen vorbesetzt sein, um eine vordefinierte Kette zu erzeugen, oder wir könnten sogar zur Laufzeit die relevanten Werte festlegen um eine frei erweiterbare sich den Bedürfnissen anpassende Kette zu erzeugen.

Stellen wir uns beispielsweise einmal vor, dass als Kontext des ersten Objektes eine 7.00%ige Steuerrate definiert ist. In diesem Fall könnte dieses Objekt feststellen, dass bei einer übergebenen Steuerrate die über dem eigenen Kontext liegt, es keinen Sinn macht, ein Objekt zu instanziieren, dass eine niedrigere Steuerrate verarbeitet. Wie implementieren wir so etwas? Eine Möglichkeit wäre, eine Tabelle mit einer Liste der Kontexte der relevanten Klassen (und Bibliotheken) vorzuhalten. Dann könnte jedes Objekt in der Kette einfach die benötigte Information nachschauen.

Zusätzlich zu den bereits ober beschriebenen Eigenschaften benötigen wir mindestens zwei Methoden:

ProcessRequest: Sichtbare Methode die genutzt wird um das Objekt aufzurufen
und die entscheidet ob eine spezielle Anfrage durch das Objekt
verarbeitet wird.
CalcTax: Die eigentliche Methode welche die Berechnung durchführt und
das Ergebnis zurückgibt.

Das Klient Objekt muss entweder über eine Referenz auf das erste Objekt der Kette verfügen oder diese erzeugen können. Es wird dann die ProcessRequest()-Methode des Objektes aufrufen und sowohl den Kontext als auch den Verkaufspreis übergeben, dessen Mehrwertsteuer benötigt wird. Der folgende Code geht davon aus, dass die Zuständigkeitskette entweder einen gültigen Mehrwertsteuerbetrag oder NULL zurück gibt:

WITH ThisForm
* Prüfen, ob das erste Objekt der Kette verfügbar ist
IF VARTYPE( This.oCalc ) # [O]
* Objekt muss erzeugt werden
.oCalc = NEWOBJECT( [TaxChain01], [chor.vcx] )
ENDIF
* Nun die ProcessRequest Methode aufrufen und sowohl Kontext als auch Preis übergeben
lnTax = .oCalc.ProcessRequest( cContext, nSalePrice )
IF ISNULL( lnTax )
* Berechnung konnte nicht durchgeführt werden
MESSAGEBOX( [Lokation konnte nicht berechnet werden], 16, [Fehlgeschlagen] )
lnTax = 0
ENDIF
RETURN lnTax
ENDWITH

Der Code innerhalb der ProcessRequest Methode ist innerhalb der abstrakten Klassendefinition hinterlegt und komplett generisch. Er entscheidet darüber, ob eine ankommende Berechnungsanforderung lokal abgearbeitet werden kann. Ist das der Fall ruft er einfach die Methode CalcTax() auf (ebenfalls innerhalb der abstrakten Klasse definiert und verarbeitet den übergebenen Preis und die eingebettete Steuerrate). Kann die Berechnung nicht lokal durchgeführt, so werden hängt die nachfolgende Aktion davon ab, ob ein anderes Objekt definiert und verfügbar ist um die Anforderung entgegenzunehmen:

LPARAMETERS tcContext, tnPrice
LOCAL lnTax
WITH This
* Können wir die Anforderung verarbeiten?
lcCanHandle = CHRTRAN( .cCanHandle, ['], [] )
IF tcContext == lcCanHandle
* Ja, also die Standardmethode CalcTax aufrufen und den Preis übergeben
lnTax = .CalcTax( tnPrice )
ELSE
* Nein, können wir nicht. Haben wir ein definiertes Folgeobjekt?
IF !EMPTY( .cNextObj ) AND !EMPTY( .cNextObjLib )
* Ja, haben wir. Aber existiert es schon?
IF VARTYPE( This.oNext ) # [O]
* Objekt erzeugen
.oNext = NEWOBJECT( .cNextObj, .cNextObjLib )
ENDIF
* Objekt aufrufen
lnTax = .oNext.ProcessRequest( tcContext, tnPrice )
ELSE
* Kein Folgeobjekt definiert, also NULL zurückliefern
lnTax = NULL
ENDIF
ENDIF
RETURN lnTax
ENDWITH


Mehr Code wird nicht benötigt und die individuellen Unterklassen für dieses sehr einfache Beispiel benötigen letztlich überhaupt keinen individuellen Code, da alles über die Einstellungen der Eigenschaften gesteuert wird (inklusive der Eigenschaft nTaxRate welche in der original Basisklasse definiert ist).

Wann sollten wir eine Kette anstelle einer Strategie benutzten?

An diesem Punkt könnten Sie vielleicht denken, „Warum soll ich mir darüber Gedanken machen?“ – insbesondere da wir bereits eine perfekte, einfache datengetriebene Lösung für das Problem im Strategiemuster gesehen haben. Nun, die Beschränkung des Strategiemusters ist, dass nur EINE Unterklasse existieren kann und deswegen ist es nicht sinnvoll, wenn wir mehrere Operationen benötigen. Das hier gezeigte Beispiel benutzte eine Ein-Schuss-Kette! Sobald ein Objekt die Anforderung bearbeiten kann wird ein Ergebnis zurückgeliefert und es wird kein weiteres Objekt mehr benötigt.

Die Zuständigkeitskette ergibt sich immer dann, wenn unter Umständen mehrere Operationen notwendig sind. Eine Erweiterung unseres einfachen Steuerproblems könnte die Verarbeitung weiterer Aufgaben wie ‚Versandkosten und Bearbeitungsgebühren‘, ‚Rabatte‘ oder sogar mehrere Steuersätze (bspw. örtliche Preisaufschläge) sein.

In solchen Situationen reicht eine Strategie nicht aus. Die Zuständigkeitskette verarbeitet sie jedoch problemlos. Alles was benötigt wird ist, anstatt beim ersten Objekt, das die Anforderung bearbeiten kann, zu stoppen, die Anforderung an jedes weitere Objekt in der Kette eindeutig weiterzureichen und jedem die Möglichkeit zu geben, an der endgültigen Lösung zu partizipieren. Natürlich kann es sein, dass wir bei einem solchen Szenario mehr als einen Rückgabewert benötigen, aber Parameterobjekte stellen eine einfache und zuverlässige Möglichkeit dar, dies zu handhaben.

Und nun hätten wir in unserer Kette Objekte die auf der Berechnung der „Mehrwertsteuersätze“, der „Versandkosten“ und der „Rabatte“ basieren. Alles was sie benötigen sind die relevanten PEMs (Eigenschaften –Properties, Ereignisse –Events, Methoden –Methods) um innerhalb der Kette aktiv zu werden.

Zuständigkeitskette Zusammenfassung

Die Zuständigkeitskette offeriert uns einen anderen Weg zur Lösung des Problems, Funktionalitäten bereitzustellen ohne diese explizit ausprogrammieren zu müssen. Der größte Vorteil der Zuständigkeitskette liegt vielleicht in der einfachen Erweiterbarkeit. Der Hauptnachteil ist hingegen das dramatische Ansteigen der aktiven Objekte im System. Wie immer, zum Abschluss, möchte ich daran erinnern, dass auch wenn die jeweiligen Implementierungsdetails variieren können, sich das Muster nicht verändern wird.

Quellennachweis:

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

Donnerstag, 18. Dezember 2008

Einführung in Entwurfsmuster (Teil3)

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 3, der sich mit dem Strategiemuster beschäftigt.

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

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

Die Strategie (Strategy)

Was ist eine Strategie und wie setze ich sie ein?

Die Strategie beschreibt eine Lösung zum Hauptproblem beim Erstellen von generischem Code – Wie gehe ich mit nicht vorhergesehenen Anforderungen und Änderungen bei der Implementierung um.

Woran erkenne ich, wo ich eine Strategie benötige?

Die formelle Definition einer Strategie, gemäß „Entwurfsmuster – Elemente wieder verwendbarer objektorientierter Software“ von Gamma, Helm, Johnson und Vlissides lautet:

Definiere eine Familie von Algorithmen, kapsele jeden einzelnen und mache sie austauschbar. Das Strategiemuster ermöglicht es, den Algorithmus unabhängig von ihn nutzenden Klienten zu variieren.
Beim ersten Lesen hört sich das ein klein wenig obskur an. Wir sollten uns ein einfaches Beispiel ansehen, bei dem ein Strategiemuster helfen könnte.

Nehmen wir das Problem, die Mehrwertsteuer für einen Einkauf zu berechnen. Wir können davon ausgehen, dass unsere Applikation weiß, welche Gegenstände verkauft wurden und die entsprechende Menge (auf Basis der Preisinformationen) und so feststellen kann, wie hoch der Einkaufsbetrag ist. Nun müssen wir die Mehrwertsteuer ausweisen bzw. berechnen.

Eigentlich gibt es da kein Problem, solange wir in einer Region leben, die nur einen Mehrwertsteuersatz kennt. Worauf zu achten ist: Wird der Gegenstand überhaupt versteuert oder nicht, und wenn ja wie ist sein augenblicklicher Mehrwertsteuersatz. Allerdings sind in den USA Mehrwertsteuersätze länder- oder sogar ortsspezifisch. Der Prozentsatz hängt davon ab, wo ich etwas kaufe (oder sogar, in einigen Fällen, wo der Sitz des Verkäufers liegt).

Beispielsweise beläuft sich in unserem Staat die Mehrwertsteuer für Kleidung auf 5.75%, aber wenn wie 20 Meilen nach Süden fahren beträgt der Steuersatz nur 5.25% und wenn wir in den nächsten Staat fahren (nur 50 Meilen entfernt) dann gibt es dort gar keine Mehrwertsteuer auf Kleidung. Ein und derselbe Gegenstand der mit $29.95 ausgeschildert ist kann uns deswegen $31.67, $31.52 oder $29.95 kosten, je nach dem, wo wir ihn kaufen. Um das ganze etwas abstrakter zu Beschreiben, die ausgewiesene Mehrwertsteuer der Transaktion hängt vom Kontext ab, in dem die Transaktion stattfindet. Würden wir nun den entsprechenden Code in der Applikation hinterlegen könnte das wie folgt aussehen:

DO CASE

CASE lcLocale = [Akron]

IF lcItemType = [Kleidung]

lnTaxRate = 5.75

ELSE

* Weitere Einträge hier

ENDIF

CASE lcLocale = [Canton]

IF lcItemType = [Kleidung]

lnTaxRate = 5.25

ELSE

* Weitere Einträge hier

ENDIF

CASE lcLocale = [Grove City]

IF lcItemType = [Kleidung]

lnTaxRate = 0.00

ELSE

* Weitere Einträge hier

ENDIF

OTHERWISE

* Standardwert zuweisen

lnTaxRate = 5.50

ENDCASE

Wir können sofort erkennen, was das Problem sein wird! Was passiert, wenn wir Cleveland zu unserer Ortsliste hinzufügen, oder wenn Akron beschließt, seine Steuer zu senken, um Canton Verkaufsanteile streitig zu machen. Wir müssen unseren Code mit allen damit verbundenen Risiken ändern. Je mehr Orte wir der Liste hinzufügen umso unübersichtlicher wird der Code. Natürlich könnten wir die Informationen innerhalb einer Tabelle hinterlegen (vielleicht eine Spalte für Orte, eine Spalte für jeden Artikel) und lesen diese Daten jedes Mal nach wenn wir sie benötigen. Auf diese Weise müssten wir nur noch einzelne Datensätze anpassen wenn sich Werte ändern, allerdings könnte das ziemlich schnell eine ganz schön große Tabelle werden und es gäbe jede Menge Duplikate und Redundanzen innerhalb dieser Datenmenge. Also auch dies wäre nicht die optimale Lösung. Was wir doch eigentlich in unserer Applikation tun wollen ist, einfach den Artikelpreis und Kontextinformationen zu übergeben, und zwar an etwas, das uns einfach nur meldet wie hoch der Mehrwertsteuerbetrag ist. Mit anderen Worten wollen wir die Abstraktion (berechne die fällige Mehrwertsteuer) von der Implementierung (basierend auf dem Ort) trennen.

Solange wir nur den Code verschieben, wird nur das Problem verlagert aber nicht gelöst. Eine einfache Brücke ist nicht die Lösung, denn sie ermöglich nur eine einzelne Implementierung und wir benötigen multiple Implementierungen. Was wir brauchen ist die Möglichkeit zur Laufzeit zu entscheiden, welche Implementierung zum Einsatz kommen soll. Das Strategiemuster bietet uns die Möglichkeit, Klassen für jede Situation zu definieren mit der wir konfrontiert werden und dann die passende zur Laufzeit zu instanzieren.

Was sind die Komponenten einer Strategie?

Eine Strategie besteht aus drei essentiellen Komponenten. Die „abstrakte Strategie“ definiert das Interface (Schnittstelle) sowie generelle Funktionalitäten. Die „konkreten Strategien“ sind die Unterklassen welche die verschiedenen möglichen Implementierungen definieren. Die dritte Komponente, der „Kontext“, ist verantwortlich für die Referenzierung auf die aktuelle Implementierung. Die Strategie wird über eine Anfrage zur Aktion durch den Klienten initiiert.



Wir können uns eine Strategie als „dynamische Brücke“ vorstellen, in welcher das eine Ende (der Kontext) statisch ist, das andere (die Strategie) jedoch aus einer Auswahl von möglichen Varianten eine Implementierung auswählt, die für den Augenblick passend ist. Die Essenz des Musters ist, das die Entscheidung, welche Unterklasse instanziert werden soll zu jeder Zeit davon abhängt, welche Informationen vom Klienten (bspw. der Applikation) benötigt werden.

Typischerweise weist das Muster die Verantwortung zur Instanzierung der konkreten Strategie dem Klienten zu, welcher dann eine Referenz an den Kontext übergibt. Um nun ein Strategiemuster zu implementieren müssen Sie eine abstrakte Strategieklasse definieren, und so viele unterschiedliche spezielle Unterklassen wie Sie benötigen. Das Objekt, welches üblicherweise die Rolle des Kontext übernimmt (in VFP ist dies üblicherweise die Form oder der Parent Container) muss dem potentiellen Klient ein passendes Interface bereitstellen. Ebenso benötigt es eine Eigenschaft die genutzt wird, um die Referenz auf das aktuelle Implementierungsobjekt aufzunehmen.

Wie implementiere ich eine Strategie?

Das folgende Beispiel zeigt auf, wie wir ein Strategiemuster als Lösung für das zuvor aufgeführte Mehrwertsteuerproblem einsetzen könnten.

Das Erste was wir tun müssen ist, dass wir eine abstrakte Klasse definieren, die über eine Eigenschaft verfügt, in der die zu verarbeitende Steuerrate hinterlegt wird. Sie verfügt zudem über eine Methode zum Berechnen der Steuer auf Basis des übergebenen Betrages und der Steuerrateneigenschaft. Nun erzeugen wir so viele Unterklassen dieser Definition wie wir Steuerratenvarianten haben – jede Variante eine Unterklasse.

Jetzt müssen wir noch herausfinden, wie wir feststellen können, wann welche der diversen Unterklassen zum Einsatz kommen soll. Um die zu können müssen wir die verschiedenen Lokationen und verfügbaren Steuerraten mit der passenden Unterklassen in Relation setzen. Natürlich gibt es viele Möglichkeiten dies zu tun und die einfachste wäre, einfach eine Liste der Lokationen (bspw. Städte) und die dort geltende Steuerrate zu erstellen. Da wir eine Unterklasse für jede Steuerrate haben können wir diese auf Basis der zur Lokation gehörenden Steuerrate herausfinden.

Da wir jedoch immer noch eine Tabelle mit einem Datensatz je Stadt benötigen, können wir den Aufwand dadurch reduzieren, dass wir unsere Unterklasse analog zur Steuerrate benamen. Dies würde in etwa wie folgt aussehen:

Stadt      Steuerrate   Unterklasse
Akron 5,75 Tax575
Canton 5,25 Tax525
Grove City 0,00 Tax000
Kent 5,75 Tax575

Mit anderen Worten nutzen wir die zugehörige Steuerrate zur Identifikation der Unterklasse. Dies hat zur Konsequenz, dass wir die dritte Spalte der Tabelle nicht mehr benötigen. Wir können die Unterklasse direkt auf Basis der Steuerrate benennen.

Wie codieren wir dies nun in einem Formular? Nun, das ist ziemlich einfach. Alles was wir brauchen ist eine DropDownListe mit den Lokationen aus unserer Stadt/Steuerraten-Tabellen. Wenn die Stadt ausgewählt wurde, erhalten wir die zugehörige Steuerrate. Hierüber wiederum generieren wir den Namen der zu instanzierenden Unterklasse, und da alle Unterklassen aus einer zentralen Basisklasse abgeleitet sind, wissen wir, dass alles was wir nur die passende Methode im Objekt mit den richtigen Parametern - in diesem Fall zwei Parameter – den Kontextschlüssel (in diesem Fall die zugeordnete Steuerrate) und den Preis zu dem die Steuern berechnet werden sollen. Der Kontextschlüssel wird genutzt um den Namen der benötigten Unterklasse zu generieren. Wenn die aktuelle Klasse nicht die richtige ist, instanzieren wir einfach die korrekte Klasse.

Hier nun der hinterlegte Code:

LPARAMETERS tcContext, tnPrice
WITH This
* Definiere den Namen des Strategieobjektes basierend auf der Basisklasse und Steuerrate
lcStrategy = [CNTSTRAT] + PADR( tcContext, 3, [0] )
* Gibt es die Klasse schon?
IF ISNULL( .oStrategy ) OR NOT UPPER( .oStrategy.Name ) == lcStrategy
* Diese Klasse muss noch erzeugt werden
.oStrategy = NEWOBJECT( lcStrategy, [Ch15.vcx] )
ENDIF
* Jetzt wird die Berechnungsmethode aufgerufen
lnTax = .oStrategy.CalcTax( tnPrice )
RETURN lnTax
ENDWITH


Dies ist nur eine Möglichkeit wie eine Strategie implementiert werden kann. Sie macht im gegebenen Beispiel durchaus Sinn, in anderen Szenarien können andere Implementierungen notwendig sein. Stellen Sie sich beispielsweise einmal die Zuweisung von Rabatten an Aufträge vor. Die Maske zur Erfassung (Der Kontext) könnte diverse unterschiedliche Rabatte innerhalb eines Auftrags benötigen:

  • Mengenrabatte, zu berechnen für einen einzelnen Artikel
  • Auftragswertrabatt, zu berechnen auf den kompletten Auftrag
  • Spezialrabatte oder Werberabatte auf einzelne Artikel
  • Kundenbezogene Rabatte auf den kompletten Auftrag

Ein mögliches Interface könnten Commandbuttons nutzen (Die Klienten) so dass der Operator entscheiden kann, wann welcher spezielle Rabatt zugewiesen werden soll. In diesem Szenario wäre es durchaus sinnvoll und auch einfacher, jeder Button kann die Referenz auf eine spezielle Rabattierungs-Strategie-Unterklasse an die Form übergeben (Hierbei sollten wir bedenken, dass diese Vorgehensweise zwar in VFP möglich ist, in anderen Sprachen geht dies jedoch nicht). Die Form könnte die die jeweils benötigte Kalkulation durchführen und das Ergebnis anzeigen, so dass der Code nicht in den Button reinkopiert werden müsste.

Strategiemuster Zusammenfassung

Wir haben eine mögliche Implementierung für ein Strategiemuster gesehen und ein anderes kurz angerissen. Der Vorteil einer Strategie ist, dass sie es umgeht, alternative Implementierungen innerhalb des Codes hinterlegen zu müssen. Sie erlaubt es uns, separate Objekte die über ein gemeinsames Interface verfügen zu erzeugen. Für jede Option erfolgt eine gezielte Instanzierung des benötigten Objekts. Wie bei anderen Mustern auch gilt, dass die jeweiligen Implementierungen verschieden sein können, das Muster sich jedoch nicht ändert.