Java für Fortgeschrittene Proseminar im Sommersemester 2009

Transcription

Java für Fortgeschrittene Proseminar im Sommersemester 2009
Java für Fortgeschrittene
Proseminar im Sommersemester 2009
Design Patterns:
Schöner Programmieren mit Design Tips
Richard Kaufhold
Technische Universität München
07.06.2009
Zusammenfassung
Diese Ausarbeitung vermittelt einige grundlegende Entwurfsmuster,
die sich in der objektorientierten Programmierung bewährt haben. Es
werden Basiskonzepte der Design Patterns an anschaulichen Beispielen
erklärt.
1
Inhaltsverzeichnis
1 Einleitung
2 Design Patterns
2.1 Creational Patterns . . .
2.1.1 Abstract Factory
2.1.2 Singleton . . . .
2.2 Structural Patterns . . .
2.2.1 Decorator . . . .
2.2.2 Facade . . . . . .
2.3 Behavioral Patterns . .
2.3.1 Observer . . . .
3
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3
4
4
8
10
10
13
14
14
3 Demoprojekt
15
4 Schluss
16
2
1
Einleitung
In unserer globalisierten Welt, in der sich Softwareentwickler behaupten müssen,
ist der Leistungsdruck immer mehr angestiegen. Die “time to market” für die
Softwareanwendungen ist in den vergangenen Jahren immer geringer geworden.
Außerdem erwarten die Kunden von den Anwendungen, die sie einsetzen, immer mehr und eine immer ausgeklügeltere Funktionalität. Deshalb können es
sich Anwendungsentwickler heute nicht mehr leisten das Rad bei jeder Anwendung neu zu entwickeln.
Aus diesen Gründen haben sich Erich Gamma, Richard Helm, Ralph Johnson
und John Vlissides vor mehr als 14 Jahren zusammengetan, um wiederkehrende
Muster in der objektorientierten Softwareentwicklung zu sammeln und ihnen
einen Namen zu geben. Das aus dieser Sammlung entstandene Werk, das oft
auch als das Buch der GoF (Gang of Four) bezeichnet wird, ist zu dem Standardwerk der Design Patterns geworden. Heutzutage sind Design Patterns aus
der Softwareentwicklung nicht mehr wegzudenken. Sie stellen einen einheitlichen
Wortschatz bereit, den jeder Entwickler für objektorientierte Software beherrschen sollte.
In der Entwicklung neuer Software sollten sich Softwarearchitekten schon frühzeitig über eine klare Struktur des zu entwerfenden Programms im klaren sein. Vorteilhaft ist es dabei, wenn sie erkannte Muster in einer sehr frühen Projektphase
beim Namen nennen. Es erleichtert anderen Programmierern, die in späteren
Projektphasen dazu kommen, die Software zu verstehen.
Java erfüllt durch seine strikte objektorientierte Architektur die besten Voraussetzungen Design Patterns gewinnbringend einzusetzten. Deshalb werde ich
in meiner Ausarbeitung einige dieser Konzepte, anhand von Java Beispielen,
vorstellen.
2
Design Patterns
Im Folgenden werde ich einen Katalog von verschiedenen Design Patterns und
deren Verwendung vorstellen. Diese Sammlung ist bei weitem nicht vollständig,
da es den Rahmen dieser Ausarbeitung sprengen würde. Deshalb möchte ich
das Buch “Design Patterns Elements of Reusable Object-Oriented Software”
([1]) für ein tiefergehendes Studium des Themas empfehlen. In diesem Buch
werden die in Abbildung 1 dargestellten Patterns vorgestellt. Diese 23 Patterns
bilden eine solide Grundlage, wenn man als Software Architekt Design Patterns
sinnvoll einsetzten will.
Wie der Abbildung zu entnehmen ist, lassen sich die Design Patterns in drei
Kategorien unterteilen: Creational Patterns, Structural Patterns und Behavioral Patterns, die alle einem bestimmten Bereich des Objekt Lebenszykluses
gewidmet sind. Zum Beginn jedes Kapitels werde ich genauer erklären, was es
mit dem jeweiligen Bereich auf sich hat.
3
Scope
Class
Creational
Factory Method
Abstract Factory
Builder
Prototype
Singleton
Purpose
Structural
Adapter(class)
Adapter(object)
Bridge
Composite
Decorator
Facade
Flyweight
Proxy
Behavioral
Interpreter
Template Method
Chain of Responsibility
Command
Iterator
Mediator
Memento
Observer
State
Strategy
Visitor

Abbildung 1: Design Pattern Übersicht
2.1
Creational Patterns
Am Anfang des Objekt Lebenszyklus benötigt ein Objek Platz im Hauptspeicher
und seine interne Datenstruktur muss initialisiert werden. Mit diesem Schritt
befassen sich die Creational Patterns. Sie spezifizieren wie der Erstellungsprozess neuer Objekte gestaltet werden kann.
Das bedeutet sie machen die resultierende Software unabhängig von der Art
wie die einzelnen Instanzen erzeugt, zusammengesetzt und repräsentiert werden. Durch diese Abstraktion erzeugt man ein größeres Maß an Unabhängigkeit
zwischen den Objekten, aus denen eine Anwendung zusammengesetzt ist. So
müssen, beim Austausch einer einzelnen Klasse, nicht mehr alle mit ihr in Verbindung stehenden Klassen geändert werden, sondern nur noch die zentrale,
erzeugende Instanz.
2.1.1
Abstract Factory
Das wohl am häufigsten dafür verwendete Pattern ist die Abstract Factory.
Sie erzeugt eine Gruppe zusammengehöriger Objekte ohne ihre konkrete Implementierung festzulegen. In Abbildung 2 ist die Struktur eines Factory Patterns
dargestellt. Der Client hat hierbei nur Zugriff auf die Abstract Factory und
die abstrakten Produkte. Er kommuniziert ausschließlich über die Interfaces
dieser Klassen. Die Klasse Abstract Factory legt für jedes abstrakte Produkt
die Signatur der Methode zum Erzeugen einer neuen Instanz fest. Es liegt nun
bei der konkreten Factory, ein passendes Objekt der mit ihr verbundenen Produktlinie zu erzeugen. Auf diese Weise ist es zum Beispiel möglich Software an
verschiedene Basissysteme anzupassen.
Um die Arbeitsweise des Factory Patterns besser zu verstehen, möchte ich das
folgende Beispiel betrachten. Es handelt sich um ein einfaches Konsolenspiel,
bei dem es das Ziel ist das Ende eines Labyrinths zu erreichen. Die Objekte,
mit denen wir es dabei zu tun haben, sind Game, Maze, Room, Wall, Door und
4
Abbildung 2: Factory Pattern
5
Abbildung 3: Maze Game
Player. Diagramm 3 stellt die Zusammenhänge der Komponenten da.
Das Spiel ist dafür verantwortlich am Spiel teilnehmende Benutzer anzulegen
und sie in den Räumen des Labyrinths zu verteilen. Die Klasse Maze erzeugt ein
Labyrinth und legt den Zielraum fest. Sobald ein Nutzer den Zielraum erreicht
hat, wird er als Sieger aus dem Spiel entfernt und die anderen Spielteilnehmer
können noch weiter versuchen das Ziel zu erreichen. Um den Schwierigkeitsgrad
etwas zu erhöhen, bekommt der Spieler jedes Mal, wenn er gegen eine Wand
oder eine geschlossene Türe läuft, einen Schlag. Wurde er zu oft getroffen, ist
das Spiel für ihn zu Ende und er hat verloren. Die Spieler kommen immer der
Reihe nach dran und können maximal eine Aktion ausführen, wie zum Beispiel
das Öffnen oder Schließen einer Tür oder das Hindurchgehen. In einer einfachen
Implementierung wird die Maze Klasse des Labyrinths mit dem “new” Operator
anlegt.
1
2
3
4
5
r 1=new
r 2=new
...
d1=new
d2=new
Room ( ) ;
Room ( ) ;
Door ( ) ;
Door ( ) ;
Auf diese Art erhalten wir ein einfaches Labyrinth. Das Problem dabei ist aber,
6
Abbildung 4: Abstract Game Factory
dass das Labyrinth nur durch das neu compilieren des Source Codes geändert
werden kann. Wir können so zum Beispiel nachträglich keine anderen Türen einbauen, die unterschiedliche Aktionen unterstützen. Besser wäre es doch dieses
Anlegen der Objekte aus dem Source Code der Maze Class auszulagern, so dass
wir die Möglichkeit erhalten die erstellten Komponenten später zu tauschen.
Dazu definieren wir uns eine abstrakte Klasse. Diese spezifiziert die Methoden,
die nötig sind, um alle Game Componenten zu erstellen. Abbildung 4 stellt diese
abstrakte Klasse dar. Die Default Game Factory erzeugt die Basis Konfiguration
mit den Standardklassen. Die Action Mod Factory tauscht die Game und die
Door Komponente aus. Dabei wird die Game Klasse um einen Befehl zum Schlagen anderer Spieler erweitert und die Türen werden in einer zufälligen Position,
d.h. geöffnet bzw. geschlossen, initialisiert.
Um den Übergang zum Factory Pattern abzuschließen, müssen wir jetzt alle
“new” Befehle durch Aufrufe der passenden Factory Methode ersetzten.
1
2
3
4
5
6
AbstractFactory factory
r 1=f a c t o r y . createRoom ( )
r 2=f a c t o r y . createRoom ( )
...
d1=f a c t o r y . c r e a t e D o o r ( )
d2=f a c t o r y . c r e a t e D o o r ( )
;
;
;
;
;
Damit ein Game Objekt erstellt werden kann, muss die verwendete Factory
7
festgelegt werden. Wie das genau umgesetzt wird, werde ich im nächsten Kapitel
zeigen.
2.1.2
Singleton
Bei der Erklärung des Factory Patterns bin ich nicht darauf eingegangen, wie es
den einzelnen Spielkomponenten ermöglicht wird, auf die Factory zuzugreifen.
Dafür gibt es verschiedene Ansatzweisen. Eine davon ist es, sie den Komponenten als Parameter zu ihrem Konstruktor zu übergeben. Diese Vorgehensweise
hat aber mehrere Schwächen. Wenn wir die Klassen auf diese Art aufbauen,
müssen wir in jeder Komponente die Factory speichern obwohl wir sie im Normalfall nach der Initialisierung nicht mehr benötigen. Wir könnten die Factory
auch als Attribut der Initialisierungsmethode übergeben. Dadurch verlieren wir
aber wieder Dynamik, da dann die Maze nach Abschluss der Initialisierung keine weiteren Komponenten mehr erzeugen kann. Sie ist also nicht im Stande sich
dynamisch zu vergrößern. Ich möchte nun das passende Design Pattern vorstellen, das die vollständige Dynamik des Systems bewahrt und es gleichzeitig
davon befreit in allen Klassen die Factory zu speichern.
Das Singleton Pattern erfüllt genau diese benötigten Anforderungen. Die allgemeine Struktur des Singleton Patterns ist in Abbildung 5 dargestellt. Wenn
wir dieses Pattern nun auf unsere Factory anwenden, müssen wir die Abstract
Factory um ein statisches Attribut vom Typ AbstractGameFactory erweitern.
Außerdem brauchen wir noch eine ebenfalls statische Zugriffsmethode die sicherstellt, dass das Singleton, wenn nötig, erstellt wird. Außerdem wollen wir
uns es freistellen eine beliebige, von AbstractGameFactory abgeleitete Klasse
als Singelton einzusetzen. Dafür definieren wir uns noch eine statische Methode
loadFactory(String name):boolean, welche die Klasse mit dem “full qualified”
Name als Singleton Instanz lädt. Diese Methode muss in jedem Fall ausgeführt
werden. Nach dem Ladevorgang ist das Singleton gesperrt, damit es zu keinen
Inkonsistenzen durch ein späteres Auswechseln des Singletons kommen kann.
Dazu muss noch gesagt werden, dass ein Singleton immer nur in Verbindung mit
dem verwendeten Classloader einzigartig ist. Verschiedene Classloader würden
mit der von mir vorgeschlagenen Version unterschiedliche Instanzen des Singletons erzeugen. Abbildung 2.1.2 zeigt die Implementierung des Singletons.
1
2
3
//..
p u b l i c a b s t r a c t c l a s s AbstractGameFactory {
s t a t i c AbstractGameFactory i n s t a n c e=n u l l ;
4
5
public s t a t i c f i n a l String
d e f a u l t F a c t o r y=DefaultGameFactory . c l a s s . getCanonicalName ( ) ;
6
7
8
9
p u b l i c s t a t i c AbstractGameFactory i n s t a n c e ( ) {
return instance ;
}
10
8
Abbildung 5: Singleton Pattern
public s t a t i c boolean loadFactory ( String
classname ) {
i f ( i n s t a n c e==n u l l ) {
try {
Class factoryClass =
C l a s s . forName ( c l a s s n a m e ) ;
i f ( f a c t o r y C l a s s != n u l l ) {
i n s t a n c e=
( AbstractGameFactory )
f a c t o r y C l a s s . newInstance ( ) ;
return true ;
}
} c a t c h ( E x c e p t i o n i e ) {}
}
return f a l s e ;
}
//...
11
12
13
14
15
16
17
18
19
20
21
22
23
24
}
Wer das Singleton Pattern aus anderen Programmiersprachen kennt, könnte
versucht sein es lazy zu erstellen. Die aus anderen Programmiersprachen bekannten Versionen des Singleton Patterns für eine faule Initialisierung sind aber
auf Grund von JVM Features und Features in der Programmiersprache falsch.
Sollten sie versucht sein Lazy Initialization einzusetzen, muss die statische Methode zum Holen der Instanz synchronisiert sein. Es gibt, meines Wissens nach,
keine andere Möglichkeit. Genauere Informationen, warum es nicht ohne weiteres möglich ist lazy loading in Java im Singleton Pattern zu implementieren,
kann in “When is a Singleton not a Singleton?” [2] nachgelesen werden.
9
Abbildung 6: Decorator Pattern
2.2
Structural Patterns
Nachdem wir uns mit dem Erstellungsprozess befasst haben, können wir uns
dem eigentlichen strukturellen Aufbau mehrerer Objekte widmen. Die Structural Patterns befassen sich damit wie Klassen und Objekte zusammengesetzt
sind, um größere Funktionsblöcke zu formen. Ein einfaches Beispiel hierfür ist die
Vererbung in objektorientierten Sprachen. Die Vererbung ist jedoch nur ein sehr
statisches Konstrukt, dass zu monolithischen Systemen führt, die nur schwer an
neue Anforderungen angepasst werden können.
2.2.1
Decorator
Das Prinzip, Objekte ineinander zu verschachteln, kann auch dazu verwendet
werden den Objekten erweiterte Funktionalitäten hinzuzufügen. Es wird oft
eingesetzt, wenn ein Objekt modelliert wird, das aus einigen Bestandteilen zusammen gesetzt ist. Die Zusammensetzung der Teile kann jedoch erst während
der Ausführung des Programms bestimmt werden. Zum Beispiel ein belegtes
Brötchen, besteht aus dem Bötchen und ein paar Sorten von Belag, die jedoch
beliebig kombiniert werden können. Wenn man ein Programm schreibt, das den
Preis berechnen soll, wird einem schnell klar, dass es sehr viele verschiedene belegte Brötchen geben kann. Man kann natürlich, die Bestandteile als Attribute
betrachten und die Preisberechnung mit Hilfe der Abfragen bewerkstelligen.
10
1
2
3
4
5
6
p u b l i c c l a s s BelegteSemmel {
p r i v a t e b o o l e a n semmel ;
private boolean kaese ;
p r i v a t e b o o l e a n wurst ;
private boolean s a l a t ;
//...
7
8
9
10
11
12
13
14
15
public int berechnePreis () {
i n t p r e i s =0;
i f ( semmel ) p r e i s +=25;
i f ( k a e s e ) p r e i s +=50;
i f ( wurst ) p r e i s +=30;
i f ( s a l a t ) p r e i s +=15;
return preis ;
}
16
17
18
19
p u b l i c v o i d setSemmel ( b o o l e a n semmel ) {
t h i s . semmel=semmel ;
}
20
21
22
23
24
1
2
3
4
5
6
7
8
9
10
p u b l i c b o o l e a n isSemmel ( ) {
r e t u r n semmel ;
}
//...
public c l a s s NaiveDecoratorClient {
p u b l i c s t a t i c v o i d main ( S t r i n g [ ] a r g s ) {
BelegteSemmel kaeseSemmel = new
BelegteSemmel ( ) ;
kaeseSemmel . setSemmel ( t r u e ) ;
kaeseSemmel . s e t K a e s e ( t r u e ) ;
kaeseSemmel . s e t S a l a t ( f a l s e ) ;
kaeseSemmel . setWurst ( f a l s e ) ;
System . out . p r i n t l n ( ” Die Kaesesemmel
k o s t e t :”+ kaeseSemmel . b e r e c h n e P r e i s ( ) ) ;
}
}
Noch sieht es ziemlich einfach aus. Was ist aber, wenn es mehr Sorten des Belages geben würde oder verschiedene Brötchen. Das Programm würde so sehr
schnell unübersichtlich und schwer wartbar werden. Und nicht nur die Klasse
BelegteSemmel, sondern auch die des Clients, weil man all die Attribute richtig setzen müsste. Einige Fälle, wie das Bestellen der doppelten Menge an Käse,
werden gar nicht abgedeckt. Eine andere Herangehensweise an das Problem bietet das Decorator Pattern.
11
Abbildung 7: Semmel Decorator
Betrachten wir Abbildung 7. Es wird mit der Klasse Zutat eine Abstraktion der
Objekte gemacht, welche auch die öffentliche Schnittstelle ist. Das Objekt die
Semmel, als Grundzutat, wird in unserem Fall, als “zu dekorierendes” spezifiziert. Da Designer Patterns keine strikte Vorgaben sind, kann die Klasse Semmel auch abstrakt definiert werden und weitere Unterklassen besitzen, wenn
man mehr als ein Brötchen modellieren muss. Die anderen Zutaten werden als
Unterklassen des ZutatenDekorators aufgefasst, weil sie einer Semmel weitere Funktionalität hinzufügen. Welche in unserem Beispiel die Erweiterung der
Preisberechnung auf beliebig viele Zutaten ist. Die Dekoratoren müssen dabei
aber genauso Zutaten sein, damit es für den Client nur eine zentrale Schnittstelle gibt. Dieser muss dann die Zutaten nur richtig verschachteln. Mit dieser
12
Implementierung ist es möglich eine beliebige Auswahl und Mengen an Zutaten hinzuzufügen. Außerdem reicht es, wenn wir einer Produktlinie eine neue
Zutat hinzufügen einen neuen Dekorator anzulegen. Wir ersparen uns damit das
Ändern der Semmel Klasse.
2.2.2
Facade
Das Facade Pattern ist sehr wichtig, wenn es darum geht, Funktionsmodule
klar voneinander abzugrenzen. Durch die Abgrenzung gewinnt man Flexibilität
in der Auswahl der verwendeten Module. Ein über eine Facade abgegrenztes
Modul kann leicht durch ein anderes ersetzt werden, das ein gleich- oder höherwertiges Interface anbietet. Die Klasse, die das Facade Muster implementiert,
ist dafür zuständig alle Objekte zu erstellen und zu verbinden, die sie für die
von ihr bereit gestellten Funktionalitäten benötigt. Es gibt die innere Struktur,
die hinter der Facade liegt, im Allgemeinen nicht nach außen hin preis.
Abbildung 8: Facade Pattern
In Abbildung 8 wird dargestellt wie sich die Einführung einer Facade in eine
Anwendung auswirken kann. Die linke Abbildung zeigt das System ohne eine Facade. Zu erkennen ist, dass die Verbindung zwischen den Objekten sehr
chaotisch stattfindet. Ein auf diese Weise strukturiertes System braucht im allgemeinen mehr Speicher und ist sehr monolithisch. Es ist nicht einfach möglich
einzelne Klassen auszutauschen, da alle verbundenen Objekte auf Seiteneffekte
hin überprüft werden müssen. Die rechte Hälfte der Abbildung 8 zeigt die, durch
13
Anwendung des Facade Patterns klar abgegrenzten Bereiche. Dadurch wird es
erleichtert Objekte hinter der Facade auszuwechseln, ohne daß Objekte dadurch
beeinflusst würden.
Beispielsweise könnte man in einer graphischen Anwendung, die Netzwerkverbindungen nutzt, vier Systeme extrahieren, die nur über Facades kommunizieren. Die vier Bereiche sind: Netzwerkkommunikation, User Interface, Persistenzebene und das Datenmodel. Das Datenmodell würde dabei die zentrale Schaltstelle darstellen, auf die alle Systeme zugreifen. Diese Abgrenzung macht Sinn,
da über das Userinterface meist keine direkte Kontrolle über die offenen Datenverbindungen des Netzwerkkontrollers benötigt werden, bzw. es keine Relevanz
für das Datenmodell hat, ob die Daten über das Netzwerk eingespielt werden
oder aus einer Datei auf der Festplatte stammen.
2.3
Behavioral Patterns
Im Kontrast zu Structural Patterns liegt der Schwerpunkt von Behavioral Patterns auf der Kommunikation zwischen verschiedenen Objekten. Sie stellen wiederkehrende Muster im Kontrollfluss von Objekten dar. Durch ihre Verwendung
wird es den Programmierern erleichtert, komplexe Zusammenhänge in einer Applikation zu verstehen. Die Behavioral Patterns lassen sich in Class- und Objektpatterns unterteilen. Class Patterns benützen Vererbung, um Teilaufgaben
an abgeleitete Klassen zu delegieren. Die Objekt Patterns setzen auf die Komposition von Objekten und sind dadurch dynamischer. In meiner Ausarbeitung
möchte ich mir das Object Behavioral Pattern Observer genauer ansehen.
2.3.1
Observer
Das Observer Pattern ermöglicht eine “One-to-Many” Kommunikation zwischen
Objekten zu etablieren. Diese Verbindung ist nicht statisch aufgebaut. Sie kann
jederzeit während der Laufzeit angepasst werden. Die Objekte, die miteinander
kooperieren möchten, müssen sich dabei über ein Thema (Subject) verständigen. Bei diesem Subject müssen sich alle beteiligten Objekte registrieren. Die
Registrierung findet in Abbildung 9 über die Methoden attach bzw. detach statt.
Die Klasse Subject hält eine Liste mit registrierten Observern vor. Nachdem
auf dem Subject die notify Methode aufgerufen wurde, ruft es bei allen registrierten Observern die update Methode auf.
Um das Pattern besser zu verstehen, möchte ich es an einem praktischen Beispiel
erklären. Dazu betrachten wir den Entwurf eines “intelligenten” Kühlschranks.
Der Kühlschrank führt dabei Protokoll über alle Entnahmen und Zugänge von
Lebensmitteln und zeigt eine Liste der vorhandenen Lebensmittel auf einem
Frontdisplay an.
Ein Ansatz das System zu entwerfen, wäre das Display fest in den Kühlschrank
zu “verdrahten”. Dieser Weg hat aber das Problem, dass ich ausschließlich Kühlschränke mit Displays verkaufen könnte. Wenn aber ein Kunde einen Kühlschrank möchte, der dazu in der Lage ist automatisch Inhalte nachzubestel-
14
Abbildung 9: Observer Pattern
len, oder er von außerhalb seines Hauses aus den Inhalt seines Kühlschrankes
überprüfen möchte, braucht er das Display nicht. Durch den höheren Preis des
Kühlschranks würde er sich gegebenenfalls für ein billigeres Konkurrenzprodukt
entscheiden. Das Ziel ist eine modularere Lösung, so dass möglichst viel der Software in allen Kühlschränken verwendet werden kann.
Das Observer Pattern bietet gerade diese Flexibilität. Es ermöglicht uns, dass
sich dynamisch viele Objekte für die Veränderungen der im Kühlschrank enthaltenen Waren registrieren können.
Abbildung 10 stellt die mit Hilfe des Observer Pattens entwickelte Struktur da.
Wie sie sehen besteht keine Komposition zwischen RefrigeratorDisplay und
dem Refrigerator selbst. Ein Display wäre so theoretisch auch dazu in der
Lage, den Inhalt von mehreren Kühlschränken anzuzeigen.
3
Demoprojekt
Die Idee meines Demoprojekts war es, eine Applikation zum kooperativen Editieren von Dokumenten zu entwickeln. Dabei sollte je nach Mime Type der
Datei ein passendes Plugin zum Anzeigen und Editieren des Dokumentes ausgewählt werden. Die Plugin Komponente implementiert gleichzeitig die Server
Funktionalität, so dass jeder Client auch als Server für Dokumente in Erscheinung treten kann. Die Benutzer Authentifizierung erfolgt durch ein zertifikat
gestütztes System, die Autorisierung durch den Server. Durch Verwendung von
15
Abbildung 10: Refrigerator
unterschiedlichen Netzwerkkonnektoren wird dem Benutzer ermöglicht sich mit
dem Server zu verbinden bzw. nach anderen Nutzern des Programms im selben
Subnetz zu suchen.
4
Schluss
Wie wir gesehen haben, können Design Patterns uns als Programmierer dabei
helfen Standardprobleme auf eine schnelle und effiziente Art zu lösen. Diese
Ausarbeitung stellt nur eine Einführung in die Thematik der Design Patterns
in objektorientierter Programmierung dar. Die GoF hat in ihrem Buch, nur
23 der bekannten Design Patterns behandelt. Es wird wahrscheinlich nie eine vollständig abgeschlossene Liste der Design Patterns geben, da stetig neue
Lösungen entwickelt werden.
Um Design Patterns vollständig zu betrachten, sollte man sich nicht nur auf
die guten Muster konzentrieren, sondern auch in der Lage sein schlechte Software und fehlerhafte Prozesse zu erkennen. Dazu hilft es die Anti-Patterns zu
betrachten, die einen guten Überblick über sehr häufig gemachte Fehler in der
16
Softwareentwicklung.
Wie man aus den Beispielen nachvollziehen kann, sind Design Patterns nicht
nur theoretische Konstrukte, die in der Realität keine Verwendung finden, sondern eine wirkliche Unterstützung des Programmierers. Sie helfen ihm dabei
sich auf die Problematik des Fachgebietes, für das er Software entwickelt, zu
konzentrieren, anstatt seine Zeit mit Standardproblemen zu verlieren.
Literatur
[1] Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides. Design Patterns Elements of Reusable Object-Oriented Software. Addison-Wesley, 2005.
[2] Joshua Fox. When is a Singleton not a Singleton? JavaWorld, 2001.
http://java.sun.com/developer/technicalArticles/Programming/singletons/.
[3] Mark Grand. Patterns in Java: a catalog of reusable design patterns illustrated with UML. Wiley & Sons, 2002.
17