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