Mittwoch, 10. Dezember 2008

Einführung in Entwurfsmuster (Teil2) / Introduction to design patterns (Part2)

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 2, der sich mit dem grundlegendsten Muster - der Brücke - 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 Brücke (Bridge)

Das erste Muster, das wir uns anschauen werden ist die Brücke, oft auch als die ‚Mutter aller Entwurfsmuster’ bezeichnet. Sie ist das grundlegendste aller Muster und je mehr Sie sich in Muster im Allgemeinen einarbeiten, werden Sie feststellen, dass sie irgendwie in fast allen anderen Mustern enthalten ist.

OK, was also ist eine Brücke?

Die formelle Definition einer Brücke, gemäß „Entwurfsmuster – Elemente wieder verwendbarer objektorientierter Software“ von Gamma, Helm, Johnson und Vlissides (GoF) lautet:
Entkoppelt eine Abstraktion von ihrer Implementierung, so dass beide unabhängig voneinander variiert werden können.
Imposant, oder? Machen wir das in unserem Code tatsächlich?

Die kurze Antwort ist, dass wir das wahrscheinlich öfter tun als wir glauben. Nehmen wir einmal ein allgemeines Beispiel. Wenn wir Code innerhalb unserer Formulare und Klassen schreiben, dann möchten wir Fehler abfangen, und üblicherweise (als pflichtbewusste Entwickler) unseren Anwendern sagen, wenn ein Problem auftritt. Die offensichtlichste und simpelste Lösung ist es ein paar Zeilen Code in der betroffenen Methode zu hinterlegen. Das Ergebnis könnte wie folgt aussehen:

IF NOT < eine Funktion die TRUE/FALSE zurückliefert >

    lcText = [ Das Feld enthält keinen korrekten Wert ] + CHR(13);

           + [ Drücken sie eine beliebige Taste für eine erneute Eingabe ]

    WAIT lcText WINDOW

    RETURN .F.

ENDIF


In unserer gesamten Applikation werden wir auf Situationen stoßen, in denen wir ein „wait window“ wie oben codiert benötigen um den Anwender zu informieren. Dies wird auch so lange perfekt funktionieren bis eines von zwei Dingen eintritt. Entweder beschließt unser Anwender, dass er diese mickrigen „Wait Windows“ hasst und viel lieber eine Messagebox im Windowsstil hätte, oder viel schlimmer, wir müssen die Applikation in eine Umgebung portieren, in der „wait window“ nicht unterstützt wird. Vielleicht als COM-Objekt oder in einem Web-Formular oder das Form wird als Toplevel Form mit Desktop = .T. gesetzt.

Wie auch immer. Nun sind wir gezwungen, unsere Applikation zu durchsuchen und überall den besagten Code an die neuen Anforderungen umzustellen. Ich weiß nicht wie es bei Ihnen ist, aber meine Erfahrung sagt mir, dass die Chance, beim ersten Mal alles richtig umzusetzen (kein Auftreten übersehen und jede einzelne Umstellung fehlerfrei), gleich Null ist. Und selbst wenn dies der Fall sein sollte bleibt uns noch die Arbeit des Prüfens jeder einzelnen Umstellung.

Ein anderes Szenario tritt ein, wenn wir mit Kollegen arbeiten. Stellen Sie sich vor, in einem meiner Formulare rufe ich eine Systemfunktion auf, und da ich ein vorsichtiger Entwickler bin prüfe ich, ob das Ergebnis wie erwartet geliefert wurde und im Fehlerfall blende ich eine Meldung wie die folgende ein:

lcText = [ Fehlerhafter Rückgabewert bei Funktionsaufruf ]

Zwischenzeitig hat mein Kollege ein ähnliches Stück Code in einem anderen Teil der Applikation geschrieben und bringt im Fehlerfall die folgende Meldung:

lcText = [ Die angeforderte Aufgabe konnte nicht durchgeführt werden ]

Auch wenn wir beide eigentlich auf die gleiche Art und Weise reagieren ist der Inhalt unterschiedlich, obwohl in beiden Fällen das Selbe passiert ist. Verwirrend? Darauf können Sie wetten. Schlimmer noch, für den Anwender sieht es unsauber aus.

Nun, was hat das alles mit dem Brückenmuster zu tun? Der Grund für unser Problem ist, dass wir nicht bemerkt haben, dass wir eigentlich eine Abstraktion (Anzeigen einer Nachricht für den Anwender) an eine Implementierung (die Befehle „Wait Window“ oder „Messagebox“) binden. Hätten wir es bemerkt, würden wir wohl eine Brücke genutzt und das Problem umgangen haben. Hier nun derselbe Code unter Einsatz eines Brückenmusters:

IF NOT <eine Funktion die TRUE/FALSE zurückliefert >

    This.oMsgHandler.ShowMessage( 9011 )

    RETURN .F.

ENDIF

Sehen Sie den Unterschied? Wir wissen nicht, oder es interessiert uns nicht, welche Art Nachricht angezeigt wird oder was im Hintergrund passiert. Alles was wir wissen müssen ist, wo wir die Referenz auf das Objekt herholen, dass die Aufgabe für uns erledigt. Und natürlich den Identifier der dem Handler mitteilt, welche Nachricht wir zu Anzeige bringen möchten. Natürlich ist es essentiell wichtig, dass alle Handler auch über das passende Interface verfügen, in diesem Fall die ShowMessage() Methode.

Es ist die Herkunft der Referenz die „Brücke“ genannt wird. In diesem Beispiel stellt die „oMsgHandler“-Eigenschaft die Brücke dar zwischen dem Code der eine Nachricht verlangt und dem Mechanismus der sich um die Nachricht kümmert. Alles was wir nun tun müssen, um die Art wie die Nachricht verarbeitet wird zu ändern, ist die Objektreferenz in der Eigenschaft zu ändern. Dies kann recht einfach zur Laufzeit durchgeführt werden, hängt jedoch von der Umgebung ab, unter der das Parentobjekt instanziert wurde (der Mechanismus der sich dahinter verbirgt ist ein Beispiel für ein anderes Muster mit dem Namen „Strategie“ (Strategy), das ich später noch ansprechen werde). Dieser Ansatz entkoppelt erfolgreich die Abstraktion von seiner Implementation und als Ergebnis erhalten wir erheblich besser wieder verwendbaren Code.

Was sind die Komponenten einer Brücke?

Eine Brücke besteht aus zwei essentiellen Komponenten, der „Abstraktion“, welche das verantwortliche Objekt für die Instanzierung der Operation darstellt, sowie der „Implementierung“ bei der es sich um das Objekt handelt, das diese ausführt. Die Abstraktion kennt ihre Implementierung entweder weil sie über eine Referenz darauf verfügt, oder weil es sie besitzt (bspw. enthält). Auch wenn es passieren kann, dass die Abstraktion die Implementierung erzeugt, so ist dies keine absolute Voraussetzung für dieses Muster. Eine Brücke kann auch eine bereits existierende Referenz zu einem Implementierungsobjekt nutzen. Tatsache ist, dass dieser Art des Einsatzes von Brücken sehr effizient sein kann. Verschiedene Abstraktionsobjekte können dasselbe Implementierungsobjekt einsetzen.

Eine weitere Tatsache ist, da Visual FoxPro über ein sehr gutes Containership Modell verfügt, so dass die meisten Entwickler feststellen werden, dass sie (unabsichtlich) schon mehr Brücken implementiert haben als sie glaubten. Die meisten ‚Manager’ Objekte (bspw. Form Manager, Ini Datei Manager, Message Manager) sind letztlich Beispiele für das Brückenmuster.



Wichtig ist, eine simple Nachrichtenweiterleitung nicht mit einer Brücke zu verwechseln. Wenn eine Methode eines Command buttons eine Methode seiner Form aufruft in der die entsprechende Aktion implementiert ist, dann handelt es sich um eine einfache Nachrichtenweiterleitung und nicht um eine Brücke. Ruft diese Formularmethode jedoch ein anderes Objekt zum Durchführen der Aktion auf, dann haben wir eine Brücke.

Interessanterweise tritt eine andere Möglichkeit auf, wenn ein Form (oder ein Container) über eine Eigenschaft verfügt, die eine Referenz auf ein Implementierungsobjekt hält. Ein enthaltenes Objekt (bspw. ein Command button auf einer Form) kann direkt auf diese Property zugreifen und eine lokale Referenz auf das Implementierungsobjekt erhalten. Hierdurch kann es direkt Methoden des Implementierungsobjektes aufrufen. Nun, handelt es sich hierbei um eine Brücke oder um Nachrichtenweiterleitung?

Tatsächlich könnten wir beide Varianten rechtfertigen. Allerdings bringt uns die Antwort nicht weiter. Dieses Problem tritt nämlich nur in Visual FoxPro auf, da es standardmäßig Eigenschaften als „Public“ bereitstellt und gleichzeitig rückwärtige Zeiger implementiert, die es Objekten erlauben, ihr Parentobjekt direkt zu adressieren. In den meisten objektorientierten Entwicklungsumgebungen ist dies schlicht und ergreifend nicht möglich, auf solche Weise auf andere Objekte zuzugreifen.

Um eine Brücke zu implementieren müssen Sie somit wissen, welches Objekt die Rolle der Abstraktion und welches die Rolle der Implementierung übernimmt. Haben sie diese erst einmal definiert, dann bleibt nur noch die Entscheidung, wie die Brücke zwischen den beiden codiert werden soll. Dies hängt dann letztlich komplett vom jeweiligen Szenario ab.

Quellennachweis:

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

Keine Kommentare:

Kommentar veröffentlichen