From 33613a85afc4b1481367fbe92a17ee59c240250b Mon Sep 17 00:00:00 2001 From: Sven Eisenhauer Date: Fri, 10 Nov 2023 15:11:48 +0100 Subject: add new repo --- .../hjp5/html/k100068.html | 1926 ++++++++++++++++++++ 1 file changed, 1926 insertions(+) create mode 100644 Master/Reference Architectures and Patterns/hjp5/html/k100068.html (limited to 'Master/Reference Architectures and Patterns/hjp5/html/k100068.html') diff --git a/Master/Reference Architectures and Patterns/hjp5/html/k100068.html b/Master/Reference Architectures and Patterns/hjp5/html/k100068.html new file mode 100644 index 0000000..bdc0326 --- /dev/null +++ b/Master/Reference Architectures and Patterns/hjp5/html/k100068.html @@ -0,0 +1,1926 @@ + + + +Handbuch der Java-Programmierung, 5. Auflage + + + + + + + + + +
 Titel  + Inhalt  + Suchen  + Index  + DOC  +Handbuch der Java-Programmierung, 5. Auflage +
 <<  +  <   +  >   + >>  + API  +Kapitel 10 - OOP IV: Verschiedenes +
+
+ + + + +

10.4 Design-Patterns

+
+ +
+ +

+Design-Patterns (oder Entwurfsmuster) +sind eine der wichtigsten und interessantesten Entwicklungen der objektorientierten +Programmierung der letzten Jahre. Basierend auf den Ideen des Architekten +Christopher Alexander wurden sie durch +das Buch »Design-Patterns - Elements of Reusable Object-Oriented +Software« von Erich Gamma, Richard +Helm, Ralph Johnson +und John Vlissides 1995 einer breiten +Öffentlichkeit bekannt. + +

+Als Design-Patterns bezeichnet man (wohlüberlegte) Designvorschläge +für den Entwurf objektorientierter Softwaresysteme. Ein Design-Pattern +deckt dabei ein ganz bestimmtes Entwurfsproblem ab und beschreibt +in rezeptartiger Weise das Zusammenwirken von Klassen, Objekten und +Methoden. Meist sind daran mehrere Algorithmen und/oder Datenstrukturen +beteiligt. Design-Patterns stellen wie Datenstrukturen oder Algorithmen +vordefinierte Lösungen für konkrete Programmierprobleme +dar, allerdings auf einer höheren Abstraktionsebene. + +

+Einer der wichtigsten Verdienste standardisierter Design-Patterns +ist es, Softwaredesigns Namen zu geben. Zwar ist es in der +Praxis nicht immer möglich oder sinnvoll, ein bestimmtes Design-Pattern +in allen Details zu übernehmen. Die konsistente Verwendung ihrer +Namen und ihres prinzipiellen Aufbaus erweitern jedoch das Handwerkszeug +und die Kommunikationsfähigkeit des OOP-Programmierers beträchtlich. +Begriffe wie Factory, Iterator oder Singleton +werden in OO-Projekten routinemäßig verwendet und sollten +für jeden betroffenen Entwickler dieselbe Bedeutung haben. + +

+Wir wollen nachfolgend einige der wichtigsten Design-Patterns vorstellen +und ihre Implementierung in Java skizzieren. Die Ausführungen +sollten allerdings nur als erster Einstieg in das Thema angesehen +werden. Viele Patterns können hier aus Platzgründen gar +nicht erwähnt werden, obwohl sie in der Praxis einen hohen Stellenwert +haben (z.B. Adapter, Bridge, Mediator, Command etc.). Zudem ist die +Bedeutung eines Patterns für den OOP-Anfänger oft gar nicht +verständlich, sondern erschließt sich erst nach Monaten +oder Jahren zusätzlicher Programmiererfahrung. + +

+Die folgenden Abschnitte ersetzen also nicht die Lektüre weiterführender +Literatur zu diesem Thema. Das oben erwähnte Werk von Gamma et +al. ist nach wie vor einer der Klassiker schlechthin (die Autoren +und ihr Buch werden meist als »GoF« bezeichnet, ein Akronym +für »Gang of Four«). Daneben +existieren auch spezifische Kataloge, in denen die Design-Patterns +zu bestimmten Anwendungsgebieten oder auf der Basis einer ganz bestimmten +Sprache, wie etwa C++ oder Java, beschrieben werden. + + + + +

10.4.1 Singleton +

+ +

+Ein Singleton ist eine Klasse, von der nur ein einziges Objekt +erzeugt werden darf. Es stellt eine globale Zugriffsmöglichkeit +auf dieses Objekt zur Verfügung und instanziert es beim ersten +Zugriff automatisch. Es gibt viele Beispiele für Singletons. +So ist etwa der Spooler in einem Drucksystem ein Singleton oder der +Fenstermanager unter Windows, der Firmenstamm in einem Abrechnungssystem +oder die Übersetzungstabelle in einem Parser. + +

+Wichtige Designmerkmale einer Singleton-Klasse sind: +

+ +

+Eine beispielhafte Implementierung könnte so aussehen: + + +

+ + + + + +
+ +
+001 public class Singleton
+002 {
+003   private static Singleton instance = null;
+004 
+005   public static Singleton getInstance()
+006   {
+007     if (instance == null) {
+008       instance = new Singleton();
+009     }
+010     return instance;
+011   }
+012 
+013   private Singleton()
+014   {
+015   }
+016 }
+
+
+Singleton.java
+ +Listing 10.10: Implementierung eines Singletons

+ +

+Singletons sind oft nützlich, um den Zugriff auf statische Variablen +zu kapseln und ihre Instanzierung zu kontrollieren. Da in der vorgestellten +Implementierung das Singleton immer an einer statischen Variable hängt, +ist zu beachten, dass es während der Laufzeit des Programms nie +an den Garbage Collector zurückgegeben und der zugeordnete Speicher +freigegeben wird. Dies gilt natürlich auch für weitere Objekte, +auf die von diesem Objekt verwiesen wird. +

+ + + + + + + + + + + +
+ +

+Manchmal begegnet man Klassen, die zwar nicht auf eine einzige, aber +doch auf sehr wenige Instanzen beschränkt sind. Auch bei solchen +»relativen Singletons«, »Fewtons« oder »Oligotons« +(Achtung, Wortschöpfungen des Autors) kann es sinnvoll sein, +ihre Instanzierung wie zuvor beschrieben zu kontrollieren. Mitunter +darf beispielsweise für eine Menge unterschiedlicher Kategorien +jeweils nur eine Instanz pro Kategorie erzeugt werden (etwa +ein Objekt der Klasse Uebersetzer +je unterstützter Sprache). Dann müssten lediglich die getInstance-Methode +parametrisiert und die erzeugten Instanzen anstelle einer einfachen +Variable in einer statischen Hashtable gehalten werden (siehe Abschnitt 14.4).

+ + + + +
 Tipp 
+
+ + + + +

10.4.2 Immutable +

+ +

+Als immutable (unveränderlich) bezeichnet man Objekte, +die nach ihrer Instanzierung nicht mehr verändert werden können. +Ihre Membervariablen werden im Konstruktor oder in Initialisierern +gesetzt und danach ausschließlich im lesenden Zugriff verwendet. +Unveränderliche Objekte gibt es an verschiedenen Stellen in der +Java-Klassenbibliothek. Bekannte Beispiele sind die Klassen String +(siehe Kapitel 11) oder +die in Abschnitt 10.2 erläuterten +Wrapper-Klassen. Unveränderliche Objekte können gefahrlos +mehrfach referenziert werden und erfordern im Multithreading keinen +Synchronisationsaufwand. + +

+Wichtige Designmerkmale einer Immutable-Klasse sind: +

+ +

+Eine beispielhafte Implementierung könnte so aussehen: + + +

+ + + + + +
+ +
+001 public class Immutable
+002 {
+003   private int      value1;
+004   private String[] value2;
+005 
+006   public Immutable(int value1, String[] value2)
+007   {
+008     this.value1 = value1;
+009     this.value2 = (String[])value2.clone();
+010   }
+011 
+012   public int getValue1()
+013   {
+014     return value1;
+015   }
+016 
+017   public String getValue2(int index)
+018   {
+019     return value2[index];
+020   }
+021 }
+
+
+Immutable.java
+ +Listing 10.11: Implementierung eines Immutable

+

+ + + + + + + + + + + +
+ +

+Durch Ableitung könnte ein unveränderliches Objekt wieder +veränderlich werden. Zwar ist es der abgeleiteten Klasse nicht +möglich, die privaten Membervariablen der Basisklasse zu verändern. +Sie könnte aber ohne weiteres eigene Membervariablen einführen, +die die Immutable-Kriterien verletzen. Nötigenfalls ist die Klasse +als final +zu deklarieren, um weitere Ableitungen zu verhindern.

+ + + + +
 Warnung 
+
+ + + + +

10.4.3 Interface +

+ +

+Ein Interface trennt die Beschreibung von Eigenschaften einer Klasse +von ihrer Implementierung. Dabei ist es sowohl erlaubt, dass ein Interface +von mehr als einer Klasse implementiert wird, als auch, dass eine +Klasse mehrere Interfaces implementiert. In Java ist ein Interface +ein fundamentales Sprachelement. Es wurde in Kapitel 9 +ausführlich beschrieben und soll hier nur der Vollständigkeit +halber aufgezählt werden. Für Details verweisen wir auf +die dort gemachten Ausführungen. + + + + +

10.4.4 Factory +

+ +

+Eine Factory ist ein Hilfsmittel zum Erzeugen von Objekten. +Sie wird verwendet, wenn das Instanzieren eines Objekts mit dem new-Operator +alleine nicht möglich oder sinnvoll ist - etwa weil das Objekt +schwierig zu konstruieren ist oder aufwändig konfiguriert werden +muss, bevor es verwendet werden kann. Manchmal müssen Objekte +auch aus einer Datei, über eine Netzwerkverbindung oder aus einer +Datenbank geladen werden, oder sie werden auf der Basis von Konfigurationsinformationen +aus systemnahen Modulen generiert. Eine Factory wird auch dann eingesetzt, +wenn die Menge der Klassen, aus denen Objekte erzeugbar sind, dynamisch +ist und zur Laufzeit des Programms erweitert werden kann. + +

+In diesen Fällen ist es sinnvoll, das Erzeugen neuer Objekte +von einer Factory erledigen zu lassen. Wir wollen nachfolgend die +drei wichtigsten Varianten einer Factory vorstellen. + + + + +

Factory-Methode

+ +

+Gibt es in einer Klasse, von der Instanzen erzeugt werden sollen, +eine oder mehrere statische Methoden, die Objekte desselben Typs erzeugen +und an den Aufrufer zurückgeben, so bezeichnet man diese als +Factory-Methoden. Sie rufen implizit den new-Operator +auf, um Objekte zu instanzieren, und führen alle Konfigurationen +durch, die erforderlich sind, ein Objekt in der gewünschten Weise +zu konstruieren. + +

+Das Klassendiagramm für eine Factory-Methode sieht so aus: +

+ + +

+ +

+Abbildung 10.1: Klassendiagramm einer Factory-Methode

+ +

+Wir wollen beispielhaft die Implementierung einer Icon-Klasse +skizzieren, die eine Factory-Methode loadFromFile +enthält. Sie erwartet als Argument einen Dateinamen, dessen Erweiterung +sie dazu verwendet, die Art des Ladevorgangs zu bestimmen. loadFromFile +instanziert ein Icon-Objekt +und füllt es auf der Basis des angegebenen Formats mit den Informationen +aus der Datei: + + +

+ + + + + +
+ +
+001 public class Icon
+002 {
+003   private Icon()
+004   {
+005     //Verhindert das manuelle Instanzieren
+006   }
+007 
+008   public static Icon loadFromFile(String name)
+009   {
+010     Icon ret = null;
+011     if (name.endsWith(".gif")) {
+012       //Code zum Erzeugen eines Icons aus einer gif-Datei...
+013     } else if (name.endsWith(".jpg")) {
+014       //Code zum Erzeugen eines Icons aus einer jpg-Datei...
+015     } else if (name.endsWith(".png")) {
+016       //Code zum Erzeugen eines Icons aus einer png-Datei...
+017     }
+018     return ret;
+019   }
+020 }
+
+
+Icon.java
+ +Listing 10.12: Eine Klasse mit einer Factory-Methode

+

+ + + + + + + + + +
+ +

+Eine Klasse mit einer Factory-Methode hat große Ähnlichkeit +mit der Implementierung des Singletons, die in Listing 10.10 +vorgestellt wurde. Anders als beim Singleton kann allerdings nicht +nur eine einzige Instanz erzeugt werden, sondern beliebig viele von +ihnen. Auch merkt sich die Factory-Methode nicht die erzeugten Objekte. +Die Singleton-Implementierung kann damit gewissermaßen als Spezialfall +einer Klasse mit einer Factory-Methode angesehen werden.

+ + + + +
 Hinweis 
+
+ + + + +

Factory-Klasse

+ +

+Eine Erweiterung des Konzepts der Factory-Methode ist die Factory-Klasse. +Hier ist nicht eine einzelne Methode innerhalb der eigenen +Klasse für das Instanzieren neuer Objekte zuständig, sondern +es gibt eine eigenständige Klasse für diesen Vorgang. Das +kann beispielsweise sinnvoll sein, wenn der Herstellungsvorgang zu +aufwändig ist, um innerhalb der zu instanzierenden Klasse vorgenommen +zu werden. Eine Factory-Klasse könnte auch sinnvoll sein, wenn +es später erforderlich werden könnte, die Factory selbst +austauschbar zu machen. Ein dritter Grund kann sein, dass es +gar keine Klasse gibt, in der eine Factory-Methode untergebracht werden +könnte. Das ist insbesondere dann der Fall, wenn unterschiedliche +Objekte hergestellt werden sollen, die lediglich ein gemeinsames Interface +implementieren. + +

+Das Klassendiagramm für eine Factory-Klasse sieht so aus: +

+ + +

+ +

+Abbildung 10.2: Klassendiagramm einer Factory-Klasse

+ +

+Als Beispiel wollen wir noch einmal das Interface DoubleMethod +aus Listing 9.14 aufgreifen. +Wir wollen dazu eine Factory-Klasse DoubleMethodFactory +entwickeln, die verschiedene Methoden zur Konstruktion von Objekten +zur Verfügung stellt, die das Interface DoubleMethod +implementieren: + + +

+ + + + + +
+ +
+001 public class DoubleMethodFactory
+002 {
+003   public DoubleMethodFactory()
+004   {
+005     //Hier wird die Factory selbst erzeugt und konfiguriert
+006   }
+007 
+008   public DoubleMethod createFromClassFile(String name)
+009   {
+010     //Lädt die Klassendatei mit dem angegebenen Namen,
+011     //prüft, ob sie DoubleMethod implementiert, und
+012     //instanziert sie gegebenenfalls...
+013     return null;
+014   }
+015 
+016   public DoubleMethod createFromStatic(String clazz,
+017                                        String method)
+018   {
+019     //Erzeugt ein Wrapper-Objekt, das das Interface
+020     //DoubleMethod implementiert und beim Aufruf von
+021     //compute die angegebene Methode der vorgegebenen
+022     //Klasse aufruft...
+023     return null;
+024   }
+025 
+026   public DoubleMethod createFromPolynom(String expr)
+027   {
+028     //Erzeugt aus dem angegebenen Polynom-Ausdruck ein
+029     //DoubleMethod-Objekt, in dem ein äquivalentes
+030     //Polynom implementiert wird...
+031     return null;
+032   }
+033 }
+
+
+DoubleMethodFactory.java
+ +Listing 10.13: Eine Factory-Klasse

+ +

+Die Anwendung einer Factory-Klasse ist hier sinnvoll, weil der Code +zum Erzeugen der Objekte sehr aufwändig ist und weil Objekte +geliefert werden sollen, die zwar ein gemeinsames Interface implementieren, +aber aus sehr unterschiedlichen Vererbungshierarchien stammen können. +

+ + + + + + + + + +
+ +

+Aus Gründen der Übersichtlichkeit wurde das Erzeugen des +Rückgabewerts im Beispielprogramm lediglich angedeutet. Anstelle +von return null; würde +in der vollständigen Implementierung natürlich der Code +zum Erzeugen der jeweiligen DoubleMethod-Objekte +stehen.

+ + + + +
 Hinweis 
+
+ + + + +

Abstrakte Factory

+ +

+Eine Abstracte Factory ist eine recht aufwändige Erweiterung +der Factory-Klasse, bei der zwei zusätzliche Gedanken im Vordergrund +stehen: +

+ +

+Eine abstrakte Factory wird auch als Toolkit +bezeichnet. Ein Beispiel dafür findet sich in grafischen Ausgabesystemen +bei der Erzeugung von Dialogelementen (Widgets) für unterschiedliche +Fenstermanager. Eine konkrete Factory muss in der Lage sein, unterschiedliche +Dialogelemente so zu erzeugen, dass sie in Aussehen und Bedienung +konsistent sind. Auch die Schnittstelle für Programme sollte +über Fenstergrenzen hinweg konstant sein. Konkrete Factories +könnte es etwa für Windows, X-Window oder die Macintosh-Oberfläche +geben. + +

+Eine abstrakte Factory kann durch folgende Bestandteile beschrieben +werden: +

+ +

+Das Klassendiagramm für eine abstrakte Factory sieht so aus: +

+ + +

+ +

+Abbildung 10.3: Klassendiagramm einer abstrakten Factory

+ +

+Das folgende Listing skizziert ihre Implementierung: + + +

+ + + + + +
+ +
+001 /* Listing1014.java */
+002 
+003 //------------------------------------------------------------------
+004 //Abstrakte Produkte
+005 //------------------------------------------------------------------
+006 abstract class Product1
+007 {
+008 }
+009 
+010 abstract class Product2
+011 {
+012 }
+013 
+014 //------------------------------------------------------------------
+015 //Abstrakte Factory
+016 //------------------------------------------------------------------
+017 abstract class ProductFactory
+018 {
+019   public abstract Product1 createProduct1();
+020 
+021   public abstract Product2 createProduct2();
+022 
+023   public static ProductFactory getFactory(String variant)
+024   {
+025     ProductFactory ret = null;
+026     if (variant.equals("A")) {
+027       ret = new ConcreteFactoryVariantA();
+028     } else if (variant.equals("B")) {
+029       ret = new ConcreteFactoryVariantB();
+030     }
+031     return ret;
+032   }
+033 
+034   public static ProductFactory getDefaultFactory()
+035   {
+036     return getFactory("A");
+037   }
+038 }
+039 
+040 //------------------------------------------------------------------
+041 //Konkrete Produkte für Implementierungsvariante A
+042 //------------------------------------------------------------------
+043 class Product1VariantA
+044 extends Product1
+045 {
+046 }
+047 
+048 class Product2VariantA
+049 extends Product2
+050 {
+051 }
+052 
+053 //------------------------------------------------------------------
+054 //Konkrete Factory für Implementierungsvariante A
+055 //------------------------------------------------------------------
+056 class ConcreteFactoryVariantA
+057 extends ProductFactory
+058 {
+059   public Product1 createProduct1()
+060   {
+061     return new Product1VariantA();
+062   }
+063 
+064   public Product2 createProduct2()
+065   {
+066     return new Product2VariantA();
+067   }
+068 }
+069 
+070 //------------------------------------------------------------------
+071 //Konkrete Produkte für Implementierungsvariante B
+072 //------------------------------------------------------------------
+073 class Product1VariantB
+074 extends Product1
+075 {
+076 }
+077 
+078 class Product2VariantB
+079 extends Product2
+080 {
+081 }
+082 
+083 //------------------------------------------------------------------
+084 //Konkrete Factory für Implementierungsvariante B
+085 //------------------------------------------------------------------
+086 class ConcreteFactoryVariantB
+087 extends ProductFactory
+088 {
+089   public Product1 createProduct1()
+090   {
+091     return new Product1VariantB();
+092   }
+093 
+094   public Product2 createProduct2()
+095   {
+096     return new Product2VariantB();
+097   }
+098 }
+099 
+100 //------------------------------------------------------------------
+101 //Beispielanwendung
+102 //------------------------------------------------------------------
+103 public class Listing1014
+104 {
+105   public static void main(String[] args)
+106   {
+107     ProductFactory fact = ProductFactory.getDefaultFactory();
+108     Product1 prod1 = fact.createProduct1();
+109     Product2 prod2 = fact.createProduct2();
+110   }
+111 }
+
+
+Listing1014.java
+ +Listing 10.14: Eine abstrakte Factory

+ +

+Bemerkenswert an diesem Pattern ist, wie geschickt es die komplexen +Details seiner Implementierung versteckt. Der Aufrufer kennt lediglich +die Produkte, die abstrakte Factory und besitzt eine Möglichkeit, +eine konkrete Factory zu beschaffen. Er braucht weder zu wissen, welche +konkreten Factories oder Produkte es gibt, noch müssen ihn Details +ihrer Implementierung interessieren. Diese Sichtweise verändert +sich auch nicht, wenn eine neue Implementierungsvariante hinzugefügt +wird. Das würde sich lediglich in einem neuen Wert im variant-Parameter +der Methode getFactory der ProductFactory +äußern. + +

+Ein wenig mehr Aufwand muss allerdings getrieben werden, wenn ein +neues Produkt hinzukommt. Dann müssen nicht nur neue abstrakte +und konkrete Produktklassen definiert werden, sondern auch die Factories +müssen um eine Methode erweitert werden. +

+ + + + + + + + + + + +
+ +

+Mit Absicht wurde bei der Benennung der abstrakten Klassen nicht die +Vor- oder Nachsilbe »Abstract« verwendet. Da die Clients +nur mit den Schnittstellen der abstrakten Klassen arbeiten und die +Namen der konkreten Klassen normalerweise nie zu sehen bekommen, ist +es vollkommen unnötig, sie bei jeder Deklaration daran zu erinnern, +dass sie eigentlich nur mit Abstraktionen arbeiten.

+ + + + +
 Tipp 
+
+

+ + + + + + + + + +
+ +

+Auch in Java gibt es Klassen, die nach dem Prinzip der abstrakten +Factory implementiert sind. Ein Beispiel ist die Klasse Toolkit +des Pakets java.awt. +Sie dient dazu, Fenster, Dialogelemente und andere plattformabhängige +Objekte für die grafische Oberfläche eines bestimmten Betriebssystems +zu erzeugen. In Abschnitt 24.2.2 +finden sich ein paar Beispiele für die Anwendung dieser Klasse.

+ + + + +
 Hinweis 
+
+ + + + +

10.4.5 Iterator +

+ +

+Ein Iterator ist ein Objekt, das es ermöglicht, die Elemente +eines Collection-Objekts nacheinander zu durchlaufen. Als Collection-Objekt +bezeichnet man ein Objekt, das eine Sammlung (meist gleichartiger) +Elemente eines anderen Typs enthält. In Java gibt es eine Vielzahl +von vordefinierten Collections, sie werden in Kapitel 14 +und Kapitel 15 ausführlich +erläutert. + +

+Obwohl die Objekte in den Collections unterschiedlich strukturiert +und auf sehr unterschiedliche Art und Weise gespeichert sein können, +ist es bei den meisten von ihnen früher oder später erforderlich, +auf alle darin enthaltenen Elemente zuzugreifen. Dazu stellt die Collection +einen oder mehrere Iteratoren zur Verfügung, die das Durchlaufen +der Elemente ermöglichen, ohne dass die innere Struktur der Collection +dem Aufrufer bekannt sein muss. + +

+Ein Iterator enthält folgende Bestandteile: +

+ +

+Das Klassendiagramm für einen Iterator sieht so aus: +

+ + +

+ +

+Abbildung 10.4: Klassendiagramm eines Iterators

+ +

+Das folgende Listing zeigt die Implementierung eines Iterators, mit +dem die Elemente der Klasse StringArray +(die ein einfaches Array von Strings kapselt) durchlaufen werden können: + + +

+ + + + + +
+ +
+001 /* Listing1015.java */
+002 
+003 interface StringIterator
+004 {
+005   public boolean hasNext();
+006   public String next();
+007 }
+008 
+009 class StringArray
+010 {
+011   String[] data;
+012 
+013   public StringArray(String[] data)
+014   {
+015     this.data = data;
+016   }
+017 
+018   public StringIterator getElements()
+019   {
+020     return new StringIterator()
+021     {
+022       int index = 0;
+023       public boolean hasNext()
+024       {
+025         return index < data.length;
+026       }
+027       public String next()
+028       {
+029         return data[index++];
+030       }
+031     };
+032   }
+033 }
+034 
+035 public class Listing1015
+036 {
+037   static final String[] SAYHI = {"Hi", "Iterator", "Buddy"};
+038 
+039   public static void main(String[] args)
+040   {
+041     //Collection erzeugen
+042     StringArray strar = new StringArray(SAYHI);
+043     //Iterator beschaffen und Elemente durchlaufen
+044     StringIterator it = strar.getElements();
+045     while (it.hasNext()) {
+046       System.out.println(it.next());
+047     }
+048   }
+049 }
+
+
+Listing1015.java
+ +Listing 10.15: Implementierung eines Iterators

+ +

+Der Iterator wurde in StringIterator +als Interface realisiert, um in unterschiedlicher Weise implementiert +werden zu können. Die Methode getElements +erzeugt beispielsweise eine anonyme Klasse, die das Iterator-Interface +implementiert und an den Aufrufer zurückgibt. Dazu wird in diesem +Fall lediglich eine Hilfsvariable benötigt, die als Zeiger auf +das nächste zu liefernde Element zeigt. Im Hauptprogramm wird +nach dem Erzeugen der Collection der Iterator beschafft und mit seiner +Hilfe die Elemente durch fortgesetzten Aufruf von hasNext +und next +sukzessive durchlaufen. +

+ + + + + + + + + +
+ +

+Die Implementierung eines Iterators erfolgt häufig mit Hilfe +lokaler oder anonymer Klassen. Das hat den Vorteil, dass alle benötigten +Hilfsvariablen je Aufruf angelegt werden. Würde die Klasse +StringArray dagegen selbst das +StringIterator-Interface implementieren +(und die Hilfsvariable index +als Membervariable halten), so könnte sie jeweils nur einen einzigen +aktiven Iterator zur Verfügung stellen.

+ + + + +
 Hinweis 
+
+

+ + + + + + + + + + + +
+ +

+Iteratoren können auch gut mit Hilfe von for-Schleifen +verwendet werden. Das folgende Programmfragment ist äquivalent +zum vorigen Beispiel: + +

+for (StringIterator it = strar.getElements(); it.hasNext(); ) {
+  System.out.println(it.next());
+}
+
+ +
+ + + + +
 Tipp 
+
+ + + + +

10.4.6 Delegate +

+ +

+In objektorientierten Programmiersprachen gibt es zwei grundverschiedene +Möglichkeiten, Programmcode wiederzuverwenden. Die erste von +ihnen ist die Vererbung, bei der eine abgeleitete Klasse alle +Eigenschaften ihrer Basisklasse erbt und deren nicht-privaten Methoden +aufrufen kann. Die zweite Möglichkeit wird als Delegation +bezeichnet. Hierbei verwendet eine Klasse die Dienste von Objekten, +aus denen sie nicht abgeleitet ist. Diese Objekte werden oft als Membervariablen +gehalten. + +

+Das wäre an sich noch nichts Besonderes, denn Programme verwenden +fast immer Code, der in anderen Programmteilen liegt, und delegieren +damit einen Teil ihrer Aufgaben. Ein Designpattern wird daraus, wenn +Aufgaben weitergegeben werden müssen, die eigentlich in der eigenen +Klasse erledigt werden sollten. Wenn also der Leser des Programms +später erwarten würde, den Code in der eigenen Klasse vorzufinden. +In diesem Fall ist es sinnvoll, die Übertragung der Aufgaben +explizit zu machen und das Delegate-Designpattern anzuwenden. +

+ + + + + + + + + +
+ +

+Anwendungen für das Delegate-Pattern finden sich meist, wenn +identische Funktionalitäten in Klassen untergebracht werden sollen, +die nicht in einer gemeinsamen Vererbungslinie stehen. Ein Beispiel +bilden die Klassen JFrame +und JInternalFrame +aus dem Swing-Toolkit (sie werden in Kapitel 36 +ausführlich besprochen). Beide Klassen stellen Hauptfenster für +die Grafikausgabe dar. Eines von ihnen ist ein eigenständiges +Top-Level-Window, das andere wird meist zusammen mit anderen Fenstern +in ein Desktop eingebettet. Soll eine Anwendung wahlweise in einem +JFrame +oder einem JInternalFrame +laufen, müssen alle Funktionalitäten in beiden Klassen zur +Verfügung gestellt werden. Unglücklicherweise sind beide +nicht Bestandteil einer gemeinsamen Vererbungslinie. Hier empfiehlt +es sich, die Gemeinsamkeiten in einer neuen Klasse zusammenzufassen +und beiden Fensterklassen durch Delegation zur Verfügung zu stellen.

+ + + + +
 Hinweis 
+
+ +

+Das Delegate-Pattern besitzt folgende Bestandteile: +

+ +

+Das Klassendiagramm für ein Delegate sieht so aus: +

+ + +

+ +

+Abbildung 10.5: Klassendiagramm eines Delegates

+ +

+Eine Implementierungsskizze könnte so aussehen: + + +

+ + + + + +
+ +
+001 /* Listing1016.java */
+002 
+003 class Delegate
+004 {
+005   private Delegator delegator;
+006 
+007   public Delegate(Delegator delegator)
+008   {
+009     this.delegator = delegator;
+010   }
+011 
+012   public void service1()
+013   {
+014   }
+015 
+016   public void service2()
+017   {
+018   }
+019 }
+020 
+021 interface Delegator
+022 {
+023   public void commonDelegatorServiceA();
+024   public void commonDelegatorServiceB();
+025 }
+026 
+027 class Client1
+028 implements Delegator
+029 {
+030   private Delegate delegate;
+031 
+032   public Client1()
+033   {
+034     delegate = new Delegate(this);
+035   }
+036 
+037   public void service1()
+038   {
+039     //implementiert einen Service und benutzt
+040     //dazu eigene Methoden und die des
+041     //Delegate-Objekts
+042   }
+043 
+044   public void commonDelegatorServiceA()
+045   {
+046   }
+047 
+048   public void commonDelegatorServiceB()
+049   {
+050   }
+051 }
+052 
+053 class Client2
+054 implements Delegator
+055 {
+056   private Delegate delegate;
+057 
+058   public Client2()
+059   {
+060     delegate = new Delegate(this);
+061   }
+062 
+063   public void commonDelegatorServiceA()
+064   {
+065   }
+066 
+067   public void commonDelegatorServiceB()
+068   {
+069   }
+070 }
+071 
+072 public class Listing1016
+073 {
+074   public static void main(String[] args)
+075   {
+076     Client1 client = new Client1();
+077     client.service1();
+078   }
+079 }
+
+
+Listing1016.java
+ +Listing 10.16: Das Delegate-/Delegator-Pattern

+ +

+Die Klasse Delegate implementiert +die Methoden service1 und service2. +Zusätzlich hält sie einen Verweis auf ein Delegator-Objekt, +über das sie die Callback-Methoden commonDelegatorServiceA +und commonDelegatorServiceB +der delegierenden Klasse erreichen kann. Die beiden Klassen Client1 +und Client2 verwenden das Delegate, +um Services zur Verfügung zu stellen (am Beispiel der Methode +service1 angedeutet). + + + + +

10.4.7 Composite +

+ +

+In der Programmierpraxis werden häufig Datenstrukturen benötigt, +bei denen die einzelnen Objekte zu Baumstrukturen zusammengesetzt +werden können. + +

+Es gibt viele Beispiele für derartige Strukturen: +

+ +

+Für diese häufig anzutreffende Abstraktion gibt es ein Design-Pattern, +das als Composite bezeichnet wird. Es ermöglicht derartige +Kompositionen und erlaubt eine einheitliche Handhabung von individuellen +und zusammengesetzten Objekten. Ein Composite enthält +folgende Bestandteile: +

+ +

+Somit sind beide Bedingungen erfüllt. Der Container ermöglicht +die Komposition der Objekte zu Baumstrukturen, und die Basisklasse +stellt die einheitliche Schnittstelle für elementare Objekte +und Container zur Verfügung. Das Klassendiagramm für ein +Composite sieht so aus: +

+ + +

+ +

+Abbildung 10.6: Klassendiagramm eines Composite

+ +

+Das folgende Listing skizziert dieses Design-Pattern am Beispiel einer +einfachen Menüstruktur: + + +

+ + + + + +
+ +
+001 /* Listing1017.java */
+002 
+003 class MenuEntry1
+004 {
+005   protected String name;
+006 
+007   public MenuEntry1(String name)
+008   {
+009     this.name = name;
+010   }
+011 
+012   public String toString()
+013   {
+014     return name;
+015   }
+016 }
+017 
+018 class IconizedMenuEntry1
+019 extends MenuEntry1
+020 {
+021   private String iconName;
+022 
+023   public IconizedMenuEntry1(String name, String iconName)
+024   {
+025     super(name);
+026     this.iconName = iconName;
+027   }
+028 }
+029 
+030 class CheckableMenuEntry1
+031 extends MenuEntry1
+032 {
+033   private boolean checked;
+034 
+035   public CheckableMenuEntry1(String name, boolean checked)
+036   {
+037     super(name);
+038     this.checked = checked;
+039   }
+040 }
+041 
+042 class Menu1
+043 extends MenuEntry1
+044 {
+045   MenuEntry1[] entries;
+046   int          entryCnt;
+047 
+048   public Menu1(String name, int maxElements)
+049   {
+050     super(name);
+051     this.entries = new MenuEntry1[maxElements];
+052     entryCnt = 0;
+053   }
+054 
+055   public void add(MenuEntry1 entry)
+056   {
+057     entries[entryCnt++] = entry;
+058   }
+059 
+060   public String toString()
+061   {
+062     String ret = "(";
+063     for (int i = 0; i < entryCnt; ++i) {
+064       ret += (i != 0 ? "," : "") + entries[i].toString();
+065     }
+066     return ret + ")";
+067   }
+068 }
+069 
+070 public class Listing1017
+071 {
+072   public static void main(String[] args)
+073   {
+074     Menu1 filemenu = new Menu1("Datei", 5);
+075     filemenu.add(new MenuEntry1("Neu"));
+076     filemenu.add(new MenuEntry1("Laden"));
+077     filemenu.add(new MenuEntry1("Speichern"));
+078 
+079     Menu1 confmenu = new Menu1("Konfiguration", 3);
+080     confmenu.add(new MenuEntry1("Farben"));
+081     confmenu.add(new MenuEntry1("Fenster"));
+082     confmenu.add(new MenuEntry1("Pfade"));
+083     filemenu.add(confmenu);
+084 
+085     filemenu.add(new MenuEntry1("Beenden"));
+086 
+087     System.out.println(filemenu.toString());
+088   }
+089 }
+
+
+Listing1017.java
+ +Listing 10.17: Das Composite-Pattern

+ +

+Die Komponentenklasse hat den Namen MenuEntry1. +Sie repräsentiert Menüeinträge und ist Vaterklasse +der spezialisierteren Menüeinträge IconizedMenuEntry1 +und CheckableMenuEntry1. Zudem +ist sie Vaterklasse des Containers Menu1, +der Menüeinträge aufnehmen kann. + +

+Bestandteil der gemeinsamen Schnittstelle ist die Methode toString. +In der Basisklasse und den elementaren Menüeinträgen liefert +sie lediglich den Namen des Objekts. In der Containerklasse wird sie +überlagert und liefert eine geklammerte Liste aller darin enthaltenen +Menüeinträge. Dabei arbeitet sie unabhängig davon, +ob es sich bei dem jeweiligen Eintrag um einen elementaren oder einen +zusammengesetzten Eintrag handelt, denn es wird lediglich die immer +verfügbare Methode toString +aufgerufen. + +

+Das Testprogramm erzeugt ein »Datei«-Menü mit einigen +Elementareinträgen und einem Untermenü »Konfiguration« +und gibt es auf Standardausgabe aus: + +

+(Neu,Laden,Speichern,(Farben,Fenster,Pfade),Beenden)
+
+ + + + + +

10.4.8 Visitor +

+ +

+Das vorige Pattern hat gezeigt, wie man komplexe Datenstrukturen mit +einer inhärenten Teile-Ganzes-Beziehung aufbaut. Solche Strukturen +müssen oft auf unterschiedliche Arten durchlaufen und verarbeitet +werden. Ein Menü muss beispielsweise auf dem Bildschirm angezeigt +werden, aber es kann auch die Gliederung für einen Teil eines +Benutzerhandbuchs zur Verfügung stellen. Verzeichnisse in einem +Dateisystem müssen nach einem bestimmten Namen durchsucht werden, +die kumulierte Größe ihrer Verzeichnisse und Unterverzeichnisse +soll ermittelt werden, oder es sollen alle Dateien eines bestimmten +Typs gelöscht werden können. + +

+All diese Operationen erfordern einen flexiblen Mechanismus zum Durchlaufen +und Verarbeiten der Datenstruktur. Natürlich könnte man +die einzelnen Bestandteile jeder Operation in den Komponenten- und +Containerklassen unterbringen, aber dadurch würden diese schnell +unübersichtlich, und für jede neu hinzugefügte Operation +müssten alle Klassen geändert werden. + +

+Das Visitor-Pattern zeigt einen eleganteren Weg, Datenstrukturen +mit Verarbeitungsalgorithmen zu versehen. Es besteht aus folgenden +Teilen: +

+ +

+Das Klassendiagramm für einen Visitor sieht so aus: +

+ + +

+ +

+Abbildung 10.7: Klassendiagramm eines Visitors

+ +

+Das folgende Listing erweitert das Composite des vorigen Abschnitts +um einen Visitor-Mechanismus: + + +

+ + + + + +
+ +
+001 /* Listing1018.java */
+002 
+003 interface MenuVisitor
+004 {
+005   abstract void visitMenuEntry(MenuEntry2 entry);
+006   abstract void visitMenuStarted(Menu2 menu);
+007   abstract void visitMenuEnded(Menu2 menu);
+008 }
+009 
+010 class MenuEntry2
+011 {
+012   protected String name;
+013 
+014   public MenuEntry2(String name)
+015   {
+016     this.name = name;
+017   }
+018 
+019   public String toString()
+020   {
+021     return name;
+022   }
+023 
+024   public void accept(MenuVisitor visitor)
+025   {
+026     visitor.visitMenuEntry(this);
+027   }
+028 }
+029 
+030 class Menu2
+031 extends MenuEntry2
+032 {
+033   MenuEntry2[] entries;
+034   int         entryCnt;
+035 
+036   public Menu2(String name, int maxElements)
+037   {
+038     super(name);
+039     this.entries = new MenuEntry2[maxElements];
+040     entryCnt = 0;
+041   }
+042 
+043   public void add(MenuEntry2 entry)
+044   {
+045     entries[entryCnt++] = entry;
+046   }
+047 
+048   public String toString()
+049   {
+050     String ret = "(";
+051     for (int i = 0; i < entryCnt; ++i) {
+052       ret += (i != 0 ? "," : "") + entries[i].toString();
+053     }
+054     return ret + ")";
+055   }
+056 
+057   public void accept(MenuVisitor visitor)
+058   {
+059     visitor.visitMenuStarted(this);
+060     for (int i = 0; i < entryCnt; ++i) {
+061       entries[i].accept(visitor);
+062     }
+063     visitor.visitMenuEnded(this);
+064   }
+065 }
+066 
+067 class MenuPrintVisitor
+068 implements MenuVisitor
+069 {
+070   String indent = "";
+071 
+072   public void visitMenuEntry(MenuEntry2 entry)
+073   {
+074     System.out.println(indent + entry.name);
+075   }
+076 
+077   public void visitMenuStarted(Menu2 menu)
+078   {
+079     System.out.println(indent + menu.name);
+080     indent += " ";
+081   }
+082 
+083   public void visitMenuEnded(Menu2 menu)
+084   {
+085     indent = indent.substring(1);
+086   }
+087 }
+088 
+089 public class Listing1018
+090 {
+091   public static void main(String[] args)
+092   {
+093     Menu2 filemenu = new Menu2("Datei", 5);
+094     filemenu.add(new MenuEntry2("Neu"));
+095     filemenu.add(new MenuEntry2("Laden"));
+096     filemenu.add(new MenuEntry2("Speichern"));
+097     Menu2 confmenu = new Menu2("Konfiguration", 3);
+098     confmenu.add(new MenuEntry2("Farben"));
+099     confmenu.add(new MenuEntry2("Fenster"));
+100     confmenu.add(new MenuEntry2("Pfade"));
+101     filemenu.add(confmenu);
+102     filemenu.add(new MenuEntry2("Beenden"));
+103 
+104     filemenu.accept(new MenuPrintVisitor());
+105   }
+106 }
+
+
+Listing1018.java
+ +Listing 10.18: Das Visitor-Pattern

+ +

+Das Interface MenuVisitor stellt +den abstrakten Visitor für Menüeinträge dar. Die Methode +visitMenuEntry wird bei jedem +Durchlauf eines MenuEntry2-Objekts +aufgerufen; die Methoden visitMenuStarted +und visitMenuEnded zu Beginn +und Ende des Besuchs eines Menu2-Objekts. +In der Basisklasse MenuEntry2 +ruft accept die Methode visitMenuEntry +auf. Für die beiden abgeleiteten Elementklassen IconizedMenuEntry +und CheckableMenuEntry gibt +es keine Spezialisierungen; auch für diese Objekte wird visitMenuEntry +aufgerufen. Lediglich der Container Menu2 +verfeinert den Aufruf und unterteilt ihn in drei Schritte. Zunächst +wird visitMenuStarted aufgerufen, +um anzuzeigen, dass ein Menüdurchlauf beginnt. Dann werden die +accept-Methoden aller Elemente +aufgerufen, und schließlich wird durch Aufruf von visitMenuEnded +das Ende des Menüdurchlaufs angezeigt. + +

+Der konkrete Visitor MenuPrintVisitor +hat die Aufgabe, ein Menü mit allen Elementen zeilenweise und +entsprechend der Schachtelung seiner Untermenüs eingerückt +auszugeben. Die letzte Zeile des Beispielprogramms zeigt, wie er verwendet +wird. Die Ausgabe des Programms ist: + +

+Datei
+ Neu
+ Laden
+ Speichern
+ Konfiguration
+  Farben
+  Fenster
+  Pfade
+ Beenden
+
+ +

+ + + + + + + + + +
+ +

+In Abschnitt 21.4.1 +zeigen wir eine weitere Anwendung des Visitor-Patterns. Dort wird +eine generische Lösung für den rekursiven Durchlauf von +geschachtelten Verzeichnisstrukturen vorgestellt.

+ + + + +
 Hinweis 
+
+ + + + +

10.4.9 Observer +

+ +

+Bei der objektorientierten Programmierung werden Programme in viele +kleine Bestandteile zerlegt, die für sich genommen autonom arbeiten. +Mit zunehmender Anzahl von Bausteinen steigt allerdings der Kommunikationsbedarf +zwischen diesen Objekten, und der Aufwand, sie konsistent zu halten, +wächst an. + +

+Ein Observer ist ein Design-Pattern, das eine Beziehung zwischen +einem Subject und seinen Beobachtern aufbaut. Als Subject +wird dabei ein Objekt bezeichnet, dessen Zustandsänderung für +andere Objekte interessant ist. Als Beobachter werden die Objekte +bezeichnet, die von Zustandsänderungen des Subjekts abhängig +sind; deren Zustand also dem Zustand des Subjekts konsistent folgen +muss. + +

+Das Observer-Pattern wird sehr häufig bei der Programmierung +grafischer Oberflächen angewendet. Ist beispielsweise die Grafikausgabe +mehrerer Fenster von einer bestimmten Datenstruktur abhängig, +so müssen die Fenster ihre Ausgabe verändern, wenn die Datenstruktur +sich ändert. Auch Dialogelemente wie Buttons, Auswahlfelder oder +Listen müssen das Programm benachrichtigen, wenn der Anwender +eine Veränderung an ihnen vorgenommen hat. In diesen Fällen +kann das Observer-Pattern angewendet werden. Es besteht aus folgenden +Teilen: +

+ +

+Das Klassendiagramm für einen Observer sieht so aus: +

+ + +

+ +

+Abbildung 10.8: Klassendiagramm eines Observers

+ +

+Das folgende Listing zeigt eine beispielhafte Implementierung: + + +

+ + + + + +
+ +
+001 /* Listing1019.java */
+002 
+003 interface Observer
+004 {
+005   public void update(Subject subject);
+006 }
+007 
+008 class Subject
+009 {
+010   Observer[] observers   = new Observer[5];
+011   int        observerCnt = 0;
+012 
+013   public void attach(Observer observer)
+014   {
+015     observers[observerCnt++] = observer;
+016   }
+017 
+018   public void detach(Observer observer)
+019   {
+020     for (int i = 0; i < observerCnt; ++i) {
+021       if (observers[i] == observer) {
+022         --observerCnt;
+023         for (;i < observerCnt; ++i) {
+024           observers[i] = observers[i + 1];
+025         }
+026         break;
+027       }
+028     }
+029   }
+030 
+031   public void fireUpdate()
+032   {
+033     for (int i = 0; i < observerCnt; ++i) {
+034       observers[i].update(this);
+035     }
+036   }
+037 }
+038 
+039 class Counter
+040 {
+041   int cnt = 0;
+042   Subject subject = new Subject();
+043 
+044   public void attach(Observer observer)
+045   {
+046     subject.attach(observer);
+047   }
+048 
+049   public void detach(Observer observer)
+050   {
+051     subject.detach(observer);
+052   }
+053 
+054   public void inc()
+055   {
+056     if (++cnt % 3 == 0) {
+057       subject.fireUpdate();
+058     }
+059   }
+060 }
+061 
+062 public class Listing1019
+063 {
+064   public static void main(String[] args)
+065   {
+066     Counter counter = new Counter();
+067     counter.attach(
+068       new Observer()
+069       {
+070         public void update(Subject subject)
+071         {
+072           System.out.print("divisible by 3: ");
+073         }
+074       }
+075     );
+076     while (counter.cnt < 10) {
+077       counter.inc();
+078       System.out.println(counter.cnt);
+079     }
+080   }
+081 }
+
+
+Listing1019.java
+ +Listing 10.19: Das Observer-Pattern

+ +

+Als konkretes Subjekt wird hier die Klasse Counter +verwendet. Sie erhöht bei jedem Aufruf von inc +den eingebauten Zähler um eins und informiert alle registrierten +Beobachter, falls der neue Zählerstand durch drei teilbar ist. +Im Hauptprogramm instanzieren wir ein Counter-Objekt +und registrieren eine lokale anonyme Klasse als Listener, die bei +jeder Benachrichtigung eine Meldung ausgibt. Während des anschließenden +Zählerlaufs von 1 bis 10 wird sie dreimal aufgerufen: + +

+1
+2
+divisible by 3: 3
+4
+5
+divisible by 3: 6
+7
+8
+divisible by 3: 9
+10
+
+ +

+ + + + + + + + + +
+ +

+Das Observer-Pattern ist in Java sehr verbreitet, denn die Kommunikation +zwischen grafischen Dialogelementen und ihrer Anwendung basiert vollständig +auf dieser Idee. Allerdings wurde es etwas erweitert, die Beobachter +werden als Listener bezeichnet, und +es gibt von ihnen eine Vielzahl unterschiedlicher Typen mit unterschiedlichen +Aufgaben. Da es zudem üblich ist, dass ein Listener sich bei +mehr als einem Subjekt registriert, wird ein Aufruf von update +statt des einfachen Arguments jeweils ein Listener-spezifisches Ereignisobjekt +übergeben. Darin werden neben dem Subjekt weitere spezifische +Informationen untergebracht. Zudem haben die Methoden gegenüber +der ursprünglichen Definition eine andere Namensstruktur, und +es kann sein, dass ein Listener nicht nur eine, sonderen mehrere unterschiedliche +Update-Methoden zur Verfügung stellen muss, um auf unterschiedliche +Ereignistypen zu reagieren. Das Listener-Konzept von Java wird auch +als Delegation Based Event Handling +bezeichnet und in Kapitel 28 +ausführlich erläutert.

+ + + + +
 Hinweis 
+
+


+ + + +
 Titel  + Inhalt  + Suchen  + Index  + DOC  +Handbuch der Java-Programmierung, 5. Auflage, Addison +Wesley, Version 5.0.1 +
 <<  +  <   +  >   + >>  + API  +© 1998, 2007 Guido Krüger & Thomas +Stark, http://www.javabuch.de +
+ + + -- cgit v1.2.3