Entwurfsmuster unter Java

Um leistungsfähige objektorientierte Modelle zu entwerfen, stehen dem Entwickler heute Entwurfsmuster hilfreich zur Seite. Speziell unter Java kann mittels Entwurfsmuster Entwicklungs­aufwand eingespart werden. Da man diese eigentlich nur im Gesamten verstehen kann, ist es schwer, dessen Nutzen direkt zu erkennen. Für diesen Beitrag wurden anhand von verschie­denen Szenerien ihr Anwendungszweck heraus gearbeitet

...page...
In jedem objektorientierten System müssen Objekte erzeugt werden. Für einen guten Entwurf ist es wichtig, kon­krete Stellen zu definieren, wo sich Ob­jekte erzeugen lassen. Alles andere kann kein guter Entwurf sein. Der Er­zeugungsprozeß kann mittels soge­nannter Erzeugungsmuster versteckt werden. Diese machen das Gesamtsy­stem davon unabhängig, wie die Objek­te erzeugt und zusammengesetzt wer­den und wie sich repräsentieren. Solche Erzeugungsmuster bestimmen konkret was, wie und wann etwas im System erzeugt wird. Das klassische Erzeu­gungsmuster verwendet Vererbung, um die Klasse des zu erzeugenden Objekts zu variieren. Das System kann so von oben herab die einzelnen Objekte konfi­gurieren und hat so einen zentralen An­satzpunkt geschaffen, ohne sich auf De­tails im Gesamtsystem beziehen zu müssen. Heute bedient sich jedes Rah­menwerk (Framework) dieser Erzeu­gungsmuster. Wir beschreiben hier das Fabrikmethoden Muster konkreter....page...

Der Praxiseinsatz „Fabrikmethode"
Dieses Muster definiert eine Schnittstel­le mit Operationen zur Erzeugung von konkreten Objekten. Jedoch unterschei­den hierbei Unterklassen, in welchen Fällen und von welchem Typ das er­zeugte Objekt ist. Ein Sammelsurium aus Erzeugungsmethoden ist dazu in den Oberklassen notwendig. Dieser Modellentwurf ist für Rahmenwerke ty­pisch. Die Außenwelt bedient sich einer Klasse des Rahmenwerks und erzeugt darüber per Erzeugungsoperationen konkrete Objekte. Stellen wir uns ein Rahmenwerk für beliebige Arten von Anwendungen vor, welche die Aufgabe haben, verschiedene Dokumentarten zu verwalten. Das Rahmenwerk abstra­hiert hierzu die Klassen „Anwendung" und „Dokument". Allgemein sind diese Klassen abstrakt definiert, damit der Klient die eigene Implementierung per Unterklassenbildung einbringen kann. Um jetzt eine Photo-Anwendung zu er­stellen, realisieren wir unsere eigenen Klassen „PhotoAnwendung" Lind „Pho­toDokument".
Dabei ist die Klasse „PhotoAnwen­dtmg" allein für die Verwaltung der Do­kumente zuständig. Auf Aufforderung wird das PhotoDokument Objekt per Menüauswahl erzeugt. Da die Doku­mentklasse unterhalb der Anwendungs­klasse angesiedelt ist, weiß diese zwar, dass etwas erzeugt werden soll, aber nicht welcher Typ von Dokument. Als Lösung kommt das Fabrikmethoden Muster wie gerufen. Es kapselt das Ob­jekterzeugungswissen der Dokument Unterklasse und lagert es aus dem Rah­menwerk in die Außenwelt aus. Die Lö­sung besteht darin, daß eine Unterklasse von der abstrakten Rahmenwerk Klasse geschaffen wird, weiche die abstrakte Methode „erzeugeDokument" über­schreibt und so die Erzeugung anstößt. Nur durch Untcrklassenbildung wird die Erzeugung möglich, wobei die exakte Klasse dem Rahmenwerk nicht be­kannt ist. Die „erzeugeDokument" Me­thode der Basisklasse nennt man Fabrik­methode, weil diese das Objekt anlegt.Die Frage ist, wie wir anwendungs­spezifische Dokumentklassen anlegen können, ohne im Rahmenwerk zu an­wendungsspezifisch zu werden. Um dies zu erreichen, kann der Entwickler im Rahmenwerk eine Klasse einführen, welche die Logik kapselt, um anwen­dungsspezifische Unterklassen von der Dokumentklasse zu erzeugen. Damit der Entwickler auch seine gerade benö­tigte Klasse aufrufen kann, muß das Rahmenwerk eine klar definierte Schnittstelle bereitstellen, welche der Entwickler dann implementiert. Lassen Sie uns einen Blick auf das Gesamtsze­wario werfen, damit die Arbeitsweise .ar wird. Das Applikationsobjekt ruft hier die „erzeugeDokument" Methode bei einem Objekt auf, welche die IDoku­mentFactory Schnittstelle implementiert hat. Dabei wird ein String übergeben, welcher der Methode mitteilt welche Unterklasse von der Klasse Dokument erzeugt werden soll. Die Applikations­klasse braucht nicht zu wissen, welche Klasse des Objekts den Methodenaufruf tätigt oder welche Dokumentklasse letztlich instanziiert wird. So ist das Rahmenwerk vollständig unabhängig von der Anwendung. Das Diagramm zeigt die einzelnen Schnittstellen und Klas­sen, die im Fabrikmethoden Muster vorkommen.Die Klasse Dokument ist eine ab­strakte Klasse des Rahmenwerks, welche durch die Fabrikmethode erzeugt wird. Das konkrete Objekt ist hier „ConcreteDocument", welches durch die Fabrikmethode instanzüert wird. Die Erzeugungsanfrage kommt von ei­ner anwendungsunabhängigen Klasse (Applikation), welche die Aufgabe hat, anwendungsspezifische Klassen anzu­legen. Hierbei bedient diese sich einer Fabrikklasse (Factory class). Die DokumentFactorv ist eine an­wendungsunabhängige ~ Schnittstelle. Diese Schnittstelle deklariert eine Me­thode, welche vom Erzeugungsobjekt (Applikation) aufgerufen wird, um ein konkretes Objekt zu erzeugen. In unse­rem Fall heißt diese „erzeugeDoku­ment". Diese Methode nimmt dabei Ar­gumente entgegen, um eine bestimmtes Objekt instanziieren zu können. Die ei­gentliche `DokumentFactory Klasse ist eine anwendungsspezifische Klasse, welche die IDokumentFactory Schnitt­stelle implementiert. Diese verfügt nun um die Methode, um ein konkretes Pro­dukt zu erzeugen. Ein Nachteil des Fa­brikmethoden Musters ist, daß jeder Klient die Erzeugungsklasse ableiten muß, nur um ein konkretes Objekt zu erzeugen. Die Bildung solcher Unter­klasse ist egal, solange der Klient die Er­zeugungsklasse ableiten n,uß. Ist dies jedoch nicht so, muß man mit den vor­geschriebenen Entwurf irgendwie zu­recht kommen. Das Fabrikmethoden Muster sollte in folgenden Fällen zum Einsatz kommen:

1) Wenn eine Klasse die Klassen von Objekten, welche diese erzeugen soll, nicht im voraus kennen kann.

2) Wenn eine Klasse möchte, daß ihre Unterklassen die von ihr zu erzeu­genden Objekte festlegt.

3) Wenn Klassen Zuständigkeiten an eine von mehreren Hilfsunterklas­sen delegieren sollen und das Wis­sen lokalisieren wollen, an welche Hilfsklasse die Zuständigkeiten de­legiert werden.

...page...

Der Praxiseinsatz „Brücke"
Besonders wichtig ist, es ein Modell zu strukturieren, damit die Öffentlichkeit den Aufbau auch verstehen kann. Zur Strukturierung hat sich mittlerweile das Brückenmuster etabliert. Überall spricht man davon, wie wichtig es ist, die Schnittstelle von der Implementierung zu trennen. Auf der Objektebene bedeu­tet dies nichts anderes, als dass ein In­terface als Typ deklariert und es inner­halb einer Klasse implementiert. Die Klasse bindet somit das Interface an sei­ne Implementierung. In der Praxis kommt es jedoch häufig vor, dass ein Typ sich auf verschiedene Art und Weise implementieren läßt. Lassen Sie uns ein Beispiel skizzieren, woran der Nut­zen des Brückenmusters ersichtlich wird_ Angenommen, wir sollen für eine Bankanwendung die Konten verwalten. Dort kommen dann Girokonten und Sparkonten vor. Jedes dieser Kontoarten kann privat oder geschäftlich eröffnet werden und hat somit verschiedene Ausprägungen. Diese Ausprägungen sind jeweils anders zu implementieren. Mit Verer­bung als Lösungsansatz kommt man hier nicht weit. Auch Schnittstellen al­leine reichen nicht aus. Es muß also ein anderer Weg her. In solchen Fällen hilft das Brückenmuster. Das Muster teilt die Schnittstelle und die Implementierung in zwei separate miteinander arbeiten­den Objekte auf. Jedoch handeln diese als ein logisches Objekt mit kombinier­ter Schnittstelle und Implementierung. Die zwei Objekte sind dabei Instanzen zweier separater Klassen (Abstraktion und Implementierer). Dabei repräsentiert die Abstraktion den Typ zur Auß­enwelt. Diese Abstraktion implemen­tiert dabei nicht die Schnittstelle, son­dern delegiert alles an die lmplementa­tion. Der Implementierer implementiert dabei die Schnittstelle, welche die Ab­straktion bereitstellt. Gewöhnlich sind solche Abstraktionen und lmplementie­rungsklassen in einer Hierarchie organi­siert. Variierende und neu definierte Abstraktion repräsentieren Untertypen des Basistyps_ Dessen Implementierer sind ebenso aufgeteilt. Die Schlüsseleigenschaft des Brük­kenmusters (Brigde) besteht darin, daß jede Abstraktion mit jedem Implemen­tierer arbeitet. So kann die Beziehung zwischen den beiden Hierarchien ober­halb dieser Hierarchie angesiedelt sein. Deshalb darf auch keine Unterklasse die Schnittstelle ändern oder erweitern, dies würde die Zusammenarbeitslogik der Hierarchie zerstören. Nur so kann eine Klasse einer Hierarchie mit jeder Klasse der anderen Hierarchie zusam­menarbeiten. Deshalb ist es wichtig, daß die Klassen in der lmplementie­rungshierarchie über die gleichen Schnittstellen verfügen. Nur so lassen sich die Objekte unabhängig variieren. Wenn eine Klassenabstraktion über mehrere Implementierungen verfügt, realisiert man dieses mittels der Verer­bung. Dabei definiert eine abstrakte Klasse die Schnittstelle, während die

Unterklassen die je­weiligen Implemen­tierungen aufnehmen. Es gibt jedoch Ent­wurfssituationen, in denen dieser Ansatz nicht flexibel genug ist. Denn die Verer­bung bindet die Im­plementierung für im­mer an die Abstraktion. Dies macht es schwierig, die Abstraktion und Imple­mentierung unabhängig zu verändern oder zu erweitern oder geschweige denn diese wiederzuverwenden.

...page...

Vererbung nur begrenzt möglich Eine Vererbung ist nicht mehr möglich, wenn eine Klasse in einer Ausprägung definiert werden soll, die nicht mehr in die Hierarchie hineinpaßt. Auf unser Konto-Beispiel bezogen kann ich schließlich nicht sagen, dass die neue Klassen von Girokonto jetzt Girokonto­Privat und GirokontoGeschäftlich heiß­en. Dies würde jeder Entwurfslogik wi­dersprechen.
Das Brückenmuster löst dieses Pro­blem, indem es die Abstraktion und dessen Implementierungen in zwei un­terschiedlichen Klassenhierarchien ver­waltet. Für unser Beispiel gibt es eine Klassenhierarchie für die allgemeinen Kontoarten: Girokonto und Sparkonto und eine davon getrennte Hierarchie für die verschiedenen Ausprägungen dieser Kontoarten. Diese` Implementie­rung verfügt über die Wurzelklasse „Kontolmpl". Die verschiedenen Unter­klassen davon implementieren die Ei­genheiten wie privat oder geschäftlich für diese Klassen. Alle Operationen von diesen Unterklassen sind auf der Basis der abstrakten Operationen der „Kon­toImpl" Schnittstelle implementiert. Dies realisiert eine Entkopplung von der Abstraktion, weil Abstraktion und Implementierung über eine Beziehung verfügen. So wir eine Brücke zwischen der Abstraktion und Implementierung gebildet und es so ermöglicht beide un­abhängig voneinander zu verwenden. Daher wird dieses Muster auch Brücken Muster genannt. Der Einsatz des Brük­kenentwurfsmusters ist sinnvoll,

1) wenn eine dauerhafte Bindung zwi­schen Abstraktion und Implemen­tierung vermieden werden soll.
2) Ebenso wenn eine Abstraktion mit verschiedenen Implementierungen kombiniert werden soll und unab­hängig crweiterbar sein sollen.
3) Die Änderung in der Abstraktion keine Auswirkungen auf die Klient­klassen hervorruft
4) sobald eine zu starke Vergrößerung der Klassenanzahl auftaucht. Solch eine Klassenhierarchie weißt schon darauf hin, daß diese aufgeteilt wer­den muß, um den Überblick zu wahren. Der Java Kode in Listing 2 zeigt das Brückenmuster in Detail.

...page...

Der Praxiseinsatz „Adapter"
Meist kommt in einem objektorientier­ten Modell vor, daß eine Klasse nicht mit einer anderen Klasse kommunzieren kann, da dessen Schnittstelle der Quell­klasse nicht bekannt ist. Im ursprüngli­chen Entwurf konnte man sich nicht vorstellen, daß beide je miteinander kommunzieren müssten. Als Lösung ist das Adapter Muster prädestiniert, da es doch noch eine kompatible Schnittstelle herstellt. Der Adapter paßt die Schnitt­stelle einer Klasse an eine andere von ih­reren Klienten erwartete Schnittstelle an. Der Adapter hat die Aufgabe, die Funktionalitäten bereitzustellen, worüber die Klient-Klasse nicht verfügt. Das Diagramm 1 zeigt, wie der Adapter dies ermöglicht. Oft kann in der Praxis eine Klasse einer Bibliothek nicht verwendet werden, weil dessen Schnittstellen nicht mit dem Anwendungsbereich übereinstimmt. Stellen Sie sich vor, Sie haben ein Buchhaltungsprogramm, welche für spezielle Funktionalitäten eine bestimmte Bibliothek verwendet. Nun kommt kurze Zeit später eine leistungsfähigere Bibliothek heraus, die Sie gerne nutzen wollen. Wegen den inkompatiblen Schnittstellen ist dies nicht möglich. Wir könnten je­doch die Klassenschnitt­stelle so ändern, daß Sie unserem Programm ent­spricht. Dies wäre aber nur möglich, wenn wir über die Quelltcxte der Bi­bliothek verfügen würden. Jedoch würde auch dies keinen Sinn ergeben, schließlich paßt mein kei­ne Bibliothek an eine An­wendung an. Wesentlich eleganter ist es, die Anwendung an die Schnittstelle der neue Bibliothek mittels einer Adapter Klasse anzupas­sen. Die Klasse realisieren wir durch Vererbung. Die­se Klasse verfügt zwar ge­genüber der Außenwelt über die gleiche Schnitt­stelle, reicht aber die An­frage intern an eine ganz andere Schnittstelle wei­ter. Beispielsweise kann eine Klasse mittels der Methode berechrteSum­mcU seine Arbeit an eine andere Klasse weiterrei­chen, wo die Methode vielleicht berech­neGesamtsumme() heißt. Da das Pro­gramm nun mit der neuen Schnittstelle arbeiten kann, kann man die neuen Funktionen nutzen.

Die Verwendung des Adapter Musters empfiehlt sich in bestimmten Situa­tionen:

1) Sobald eine existieren­de Klasse verwendet werden soll, deren Schnittstelle aber nicht mit der benötigten Schnittstelle überein­stimmt.

2) Eine wiederverwertd­bare Klasse realisiert werden soll, die mit unbekannten Klassen interoperabel sein soll.

Ein Adapter Objekt bietet die Öffentliche Schnittstelle, welche die Außenwelt ver­langt. Dabei hat diese keine Kenntnisse, welche Klasse als Implementierung für die Schnittstelle verwendet wird. Folgende Rollen nehmen die Klas­sen in einem Adapter Szenario ein und werden anhand des Diagramms ersicht­lich. Der Clicnt repräsentiert eine Klas­se, welche eine Methode einer anderen Klasse aufruft mittels der Schnittstelle (TargetInterface). Dies besagt aber noch nicht, daß eine spezielle Klasse letztlich verwendet und aufgerufen wird. Die Schnittstelle (Targetlnterface) deklariert all die Methoden, deren sich der Client bedienen kann. Die Adapterklasse im­plementiert diese Schnittstelle, aber nicht deren Funktionalität. Alle Arbei­ten delegiert diese an die Adaptee-Klas­se weiter. Die Adaptee-Klasse verfügt über die Methode, die der Client haben will. Dessen Schnittstelle ist völlig un­abhängig von den anderen. Eine Adapter Klasse hat jedoch wei­tere Möglichkeiten als nur einen Metho­denaufruf zu delegieren. Diese kann mittels übergebener Argumenten ver­
sdiiedene Transformationen vorneh­men. Hierüber kann man zusätzlich Lo­gik implementieren, worüber sich die Unterschiede zwischen dem Aufbau der Methoden beim Interface und der Adaptee-Klasse verbergen lassen. Je­doch gibt es keine Einschränkungen, wie komplex so eine Adapter Klasse sein soll. Das Basisziel ist es aber die Me­thodenaufrufe an andere Objekte weiter­zuleiten mittels der Adapter Klasse. Die Implementierung einer Adapter Klasse ist eigentlich ziemlich einfach. Das einzige was zu beachten ist, wie die Adapter Klasse informiert wird, welche Adaptee-Klasse diese aufrufen soll.

Hierzu gibt es zwei Vorgehensweisen:

1) Indem eine Referenz als Parameter im Konstruktor oder einer seiner Methoden übergeben wird. Dies er­laubt dem Adapterobjekt mit jeder Instanz oder mehreren Instanzen der Adaptee-Klasse zu verwenden.

2) Das Erzeugen der Adapter Klasse als innere Klasse von Adaptec-Klas­se. Dies vereinfacht die Assoziation zwischen dem Adapterobjekt und dem Adapteeobjekt, da diese auto­matisch jetzt erfolgt. Gleichzeitig macht solch eine Assoziation jedoch inflexibel.