In diesem Abschnitt soll gezeigt werden, welche Teilaufgaben mit Hilfe von Entwurfsmustern bewältigt werden können. Diese Teilaspekte können jedoch schlecht isoliert gezeigt werden. Ich habe es deshalb auch nicht versucht, sondern vielmehr die Beispiele genutzt, um ein paar Muster genauer vorzustellen. Auch wenn jedes Muster es Wert wäre, besprochen zu werden, so macht es keinen Sinn, dies in drei Seminarvorträgen zu tun. Wer komplexe objektorientierte Entwürfe realisieren muß (oder will), sollte sich den Katalog unbedingt selbst einmal durchlesen !
Genau ! Denn die Strukturen in den Entwurfsmustern sind (bei allem Respekt, Herr Gamma !) nicht revolutionär, sondern sie bieten eine gute Möglichkeit, ohne Umwege zu guten Entwürfen zu gelangen.
Genau wie es keinen Algorithmus zum Entwurf von Algorithmen gibt, so gibt es kein Muster für den Einsatz von Entwurfsmustern. Es gibt praktisch nur eine Methode: Man liest (bzw. kennt) alle Muster, insbesondere deren Abschnitt über die Anwendbarkeit, und entscheidet, ob die Problemstellung und das Muster aufeinander passen. Ein hinreichendes Wissen über die Klassifizierung der Muster und ihre Beziehungen untereinander ermöglicht es natürlich, das Finden eines passenden Musters nicht dem Zufall zu überlassen.
Der naheliegendste, aber nicht zwingend beste Ansatz zur Bestimmung der Objektstruktur besteht in der Nachbildung der Problemstellung. Gamma übertreibt wie folgt:
"Sie können die Problemstellung aufschreiben, die Substantive und Verben unterstreichen und ihnen entsprechende Klassen und Operationen konstruieren."
Dieses (oder zumindest ähnliches) Vorgehen kann sich jedoch auf die Flexibilität
des Entwurfes negativ auswirken. In vielen Fällen kann es sinnvoll
sein, Objekte einzuführen, die keine Entsprechung in der Problemstellung
haben, jedoch maßgeblich dazu beitragen, spätere Änderungen
im Entwurf leicht realisieren zu können. Dies können beispielsweise
Objekte sein, die Algorithmen oder verschiedenartige, aber ähnliche
Objekte auf einer höheren Abstraktionsebene repräsentieren oder
aber solche, die ihrerseits für die Erzeugung anderer Objekte zuständig
sind.
Solche Objekte in der Struktur eines Entwurfes entstehen in der Regel erst
im fortgeschrittenen Entwicklungsstadium. Da sie aber oft das Resultat
der "Evolution" eines Entwurfes sind, ist es effizienter, sie
bereits frühzeitig zu berücksichtigen. (Zum Beispiel durch den
Gebrauch der Entwurfsmuster ...)
Beispiel:
In einem Programm zur Mitarbeiterverwaltung gibt es drei Arten von Mitarbeitern, die in drei Klassen implementiert sind. Die Struktur der Objektvariablen ist gleich, nur der Algorithmus zur Gehaltsberechnung ist verschieden.
Da die verschiedenen Mitarbeiterklassen in diversen Kontexten innerhalb des Programmes verwendet werden sollen, ist es sinnvoll, eine abstrakte Klasse einzuführen, von der man die anderen durch Vererbung ableitet. Achtung: Diese Veränderung ist nicht das Ergebnis der Verwendung von Entwurfsmustern; die Bildung von abstrakten Klassen ist in jedem komplexen objektorientierten Entwurf sinnvoll !
Erst jetzt kommen die Entwurfsmuster (bzw. eines davon) ins Spiel. Das objektbasierte Verhaltensmuster "Strategie" gibt uns in diesem Fall vor, wie wir unsere Objektstruktur flexibler machen können. Im Anwendbarkeits-Abschnitt dieses Musters lesen wir:
" (...) , wenn sich viele verwandte Klassen nur in ihrem Verhalten unterscheiden (...) "
Dies ist hier der Fall. Ob Chef oder Azubi, die Struktur
des Datensatz-Objektes ist stets die gleiche. Lediglich die Berechnung
des Gehaltes folgt verschiedenen Regeln. Der Strukturabschnitt dieses Musters
zeigt uns, wie wir Algorithmen in Objekte kapseln:
Ein "Mitarbeiter"-Objekt erhält zusätzlich eine Referenz
auf ein Strategieobjekt, welches hier durch eine
abstrakte Oberklasse "Gehaltsermittler"
vertreten wird. Eine Anfrage eines Klienten nach der Höhe des Gehaltes
leitet das "Mitarbeiter"-Objekt an seinen "Gehaltsermittler"
weiter. Hierbei übergibt es diesem eine Referenz auf sich selbst,
damit der Algorithmus Zugriff auf die Objektvariablen des "Mitarbeiters"
hat.
Somit ist eine Struktur entstanden, die nur noch eine "Mitarbeiter"-Klasse kennt und in der die Menge der Algorithmen zur Berechnung des Gehaltes beliebig veränderbar ist.
Bei der Festlegung, wie fein eine Informationsstruktur
in Objekte untergliedert werden soll
(Objektgranularität), hat ein
Entwickler die Qual der Wahl: Dem Objekt ist es "egal",
es ist ein virtuelles Gebilde und kann quasi alles "sein". Natürlich
ist nicht jede theoretisch denkbare Lösung sinnvoll. Diverse Muster
bieten Hilfen für diesen Aspekt.
Beispiel: Fliegengewicht
(Ich verwende zur Beschreibung der Struktur des Fliegengewicht-Musters das gleiche
Beispiel wie die "Gang Of Four", nämlich das eines WYSIWYG-Dokumenteditors,
da ich mir kein Beispiel vorstellen kann, welches die Motivation des
Fliegengewicht-Musters deutlicher zeigt.)
In diesem WYSIWYG-Editor ist es theoretisch denkbar (wenn auch selten der Fall), daß
einzelne Zeichen gesondert formatiert werden sollen. Ein guter Dokumenteditor muß diese
Möglichkeit auf jeden Fall berücksichtigen. In einem objektorientierten
Softwareentwurf bedeutet dies, das jedes Zeichen durch ein eigenes Objekt repräsentiert
wird, da es eigene Eigenschaften wie Schriftart, Größe oder Farbe besitzen
kann. Diese Zeichen-Objekte sind in der Lage, sich selbst unter Berücksichtigung
ihrer Eigenschaften auf dem Bildschirm (,Drucker,...) zu zeichnen.
Die Spezifikation der Schnittstellen (Glossar: Polymorphie,
Schnittstelle, Signatur,
Typ !!!) ist von zentraler Bedeutung.
Die Schnittstelle ist das einzig sichtbare an einem Objekt. Nicht nur für
die Arbeit im Team ist es wichtig, die Schnittstellen innerhalb der Objektstruktur
sauber zu definieren.
Beispiel:
Ein Muster, bei dem die Gestaltung der Schnittstellen sehr wichtig
(weil komplex !) ist, ist das Memento-Muster. Es dient dazu, den internen
Zustand (=Variablenbelegung) eines Objektes festzuhalten und ggf. das Objekt
später mit den festgehaltenen Daten zu belegen. Dies kann beispielsweise
für die Realisierung von UNDO-Operationen notwendig sein. Nachfolgend
ist eine Struktur abgebildet, die das Memento-Muster verwendet.
Der Klient möchte den Zustand des UrheberObjektes speichern,
hat aber keinen Zugriff auf die Variablen, da er lediglich eine
Referenz
auf dieses Objekt besitzt. Um dem Klienten jedoch die Verwaltung des gespeicherten
Zustandes zu ermöglichen, ohne die Kapselung zu zerstören, erweitert
man die Klassenhierarchie um eine zusätzliche Klasse, das Memento.
Eine Verwendung dieser Struktur läuft wie folgt ab:
Mit dieser Struktur ist die Kapselung beibehalten worden, da der
Klient das Memento zwar verwaltet, seine Struktur jedoch nicht kennt und
so weder auf dessen Variablen zugreifen, noch dessen Methoden aufrufen
kann.
Die bisher von mir vorgestellten vier Muster sind Struktur- bzw. Verhaltensmuster (Mehr
zu dieser Unterteilung in Abschnitt 5). Der Vollständigkeit
und ihrer Bedeutung halber möchte ich auch noch ein Erzeugungsmuster vorstellen.
Das Prototyp-Muster beschreibt, wie man den Prozeß der Objekterzeugung vom
eigentlichen System entkoppeln kann, ohne eine zusätzliche Klassenhierarchie von
erzeugenden Klassen aufzubauen. Stattdessen definiert man eine
abstrakte Klasse, einen
"Prototypen", der eine Methode enthält, die eine Kopie des entsprechenden
Objektes (nicht eine Referenz auf es)
zurückgibt. Jedes Klasse, welche von
diesem abstrakten Prototypen abgeleitet ist, implementiert die Methode zum
"Klonen" speziell für sich und macht sich somit ebenfalls zum Prototypen.
Dies hat den Vorteil, daß die Menge der Prototypen (auch zur Laufzeit !) einfach
zu verwalten (und zu erweitern !) ist.
2. Klassenbildung, Bestimmung der Objektgranularität
Die Idee des Fliegengewicht-Musters besteht darin, diejenigen Informationen, die in der
Regel nicht nur ein Objekt, sondern größere Mengen von ihnen betreffen,
in seinen Kontext zu verlagern.
In unserem Beispiel bedeutet dies, daß ein in der Objekthierarchie des Dokumentes
weiter oben stehendes Objekt (Zeile, Absatz oder allgemein: Block) die Werte der o.a.
Eigenschaften (d.h. den Kontext) sowie Referenzen auf die enthaltenen Zeichen-Objekte
speichert.
Doch damit nicht genug: Durch diese Struktur wird es möglich, von jedem im Dokument
vorhandenen Zeichen nur ein Objekt bereitzustellen. Jede größere
Struktur referenziert so die entsprechenden Zeichen-Objekte (typischerweise jeweils
mehrmals) und delegiert (u.a.) das Zeichnen
(unter Angabe der Eigenschaften als Parameter)
an diese. Falls einzelne Zeichen besonders formatiert werden, kostet diese Aufteilung
natürlich Ressourcen, da statt eines einzelnen Zeichen-Objektes zusätzlich
ein Objekt benötigt wird, welches den Kontext des Zeichens speichert und das Zeichnen
(etc.) delegiert.
Dies sollte aber nicht der Normalfall sein, und gerade für diesen soll unsere Struktur
optimiert sein.
Die folgende Abbildung zeigt den Ausschnitt einer solchen, hierarchischen
Dokumentstruktur:
3. Spezifikation der Schnittstellen
Ein Erzeugungsmuster: Prototyp
Wird diese Technik von einem Framework benutzt, so
kann der Nutzer dieses Frameworks, also der Entwickler, leicht die zur Verfügung
stehende Klassenhierarchie erweitern, ohne eine Klassenhierarchie von erzeugenden
Klassen hinzufügen zu müssen. Ein gutes Beispiel bieten hier Frameworks
zur Entwicklung von Zeichenanwendungen. Die Zeichenelemente (elektronische Bausteine in
einer Anwendung zum Schaltkreisentwurf, Piktogramme für Möbelstücke in
einer "Inneneinrichtungs-Anwendung", ...) werden hier durch die entsprechenden
Prototypen implementiert. So können auch
Bibliotheken von Zeichenelementen angelegt
werden. Nachstehend ist die allgemeine Struktur des Prototyp-Musters gezeigt. Zu
beachten ist hier, daß der Klient Referenzen zu allen Prototypen (nicht nur zu deren
abstrakter Klasse) unterhalten muß,
um mit diesen arbeiten zu können.
[Seminarübersicht]...[Entwurfsmuster nach Gamma]...[5 Der Entwurfsmusterkatalog]...[Seitenanfang]