Rheinwerk Computing < openbook >


 
Inhaltsverzeichnis
Materialien
Vorwort
1 Java ist auch eine Sprache
2 Imperative Sprachkonzepte
3 Klassen und Objekte
4 Arrays und ihre Anwendungen
5 Der Umgang mit Zeichenketten
6 Eigene Klassen schreiben
7 Objektorientierte Beziehungsfragen
8 Ausnahmen müssen sein
9 Geschachtelte Typen
10 Besondere Typen der Java SE
11 Generics<T>
12 Lambda-Ausdrücke und funktionale Programmierung
13 Architektur, Design und angewandte Objektorientierung
14 Java Platform Module System
15 Die Klassenbibliothek
16 Einführung in die nebenläufige Programmierung
17 Einführung in Datenstrukturen und Algorithmen
18 Einführung in grafische Oberflächen
19 Einführung in Dateien und Datenströme
20 Einführung ins Datenbankmanagement mit JDBC
21 Bits und Bytes, Mathematisches und Geld
22 Testen mit JUnit
23 Die Werkzeuge des JDK
A Java SE-Module und Paketübersicht
Stichwortverzeichnis


Download:

- Listings, ca. 2,7 MB


Buch bestellen
Ihre Meinung?



Spacer
<< zurück
Java ist auch eine Insel von Christian Ullenboom

Einführung, Ausbildung, Praxis
Buch: Java ist auch eine Insel


Java ist auch eine Insel

Pfeil 13 Architektur, Design und angewandte Objektorientierung
Pfeil 13.1 Architektur, Design und Implementierung
Pfeil 13.2 Design-Patterns (Entwurfsmuster)
Pfeil 13.2.1 Motivation für Design-Patterns
Pfeil 13.2.2 Singleton
Pfeil 13.2.3 Fabrikmethoden
Pfeil 13.2.4 Das Beobachter-Pattern mit Listener realisieren
Pfeil 13.3 Zum Weiterlesen
 

Zum Seitenanfang

13.2    Design-Patterns (Entwurfsmuster) Zur vorigen ÜberschriftZur nächsten Überschrift

Aus dem objektorientierten Design haben wir gelernt, dass Klassen nicht fest miteinander verzahnt, sondern lose gekoppelt sein sollen. Das bedeutet, Klassen sollten nicht zu viel über andere Klassen wissen, und die Interaktion sollte über wohldefinierte Schnittstellen erfolgen, sodass die Klassen später noch verändert werden können. Die lose Kopplung hat viele Vorteile, da so die Wiederverwendung erhöht und das Programm änderungsfreundlicher wird. Wir wollen dies an einem Beispiel prüfen.

In einer Datenstruktur sollen Kundendaten gespeichert werden. Zu dieser Datenquelle gibt es eine grafische Oberfläche, die diese Daten anzeigt und verwaltet, etwa eine Eingabemaske. Wenn Daten eingegeben, gelöscht und verändert werden, sollen sie in die Datenstruktur übernommen werden. Den anderen Weg von der Datenstruktur in die Visualisierung werden wir gleich beleuchten. Bereits jetzt haben wir eine Verbindung zwischen Eingabemaske und Datenstruktur, und wir müssen aufpassen, dass wir uns im Design nicht verzetteln, denn vermutlich läuft die Programmierung darauf hinaus, dass beide fest miteinander verbunden sind. Wahrscheinlich wird die grafische Oberfläche irgendwie über die Datenstruktur Bescheid wissen, und bei jeder Änderung in der Eingabemaske werden direkt Methoden der konkreten Datenstruktur aufgerufen. Das wollen wir vermeiden. Genauso haben wir nicht bedacht, was passiert, wenn nun infolge weiterer Programmversionen eine grafische Repräsentation der Daten, etwa in Form eines Balkendiagramms, gezeichnet wird. Und was geschieht, wenn der Inhalt der Datenstruktur über eine andere Programmstelle geändert wird und dann einen Neuaufbau der Bildschirmdarstellung erzwingt? Hier verfangen wir uns in einem Knäuel von Methodenaufrufen, und änderungsfreundlich ist dies dann auch nicht mehr. Was ist, wenn wir nun unsere selbst gestrickte Datenstruktur durch eine SQL-Datenbank ersetzen wollen?

 

Zum Seitenanfang

13.2.1    Motivation für Design-Patterns Zur vorigen ÜberschriftZur nächsten Überschrift

Die Gedanken über grundlegende Designkriterien gehen weit zurück. Vor dem objektorientierten Programmieren (OOP) gab es das strukturierte Programmieren, und die Entwickler waren froh, mit Werkzeugen schneller und einfacher Software bauen zu können. Auch die Assembler-Programmierer waren erfreut, strukturiertes Programmieren zur Effizienzsteigerung einsetzen zu können – sie setzten ja auch Unterprogramme nur deswegen ein, weil sich mit ihnen wieder ein paar Bytes sparen ließen. Doch nach Assembler und dem strukturierten Programmieren sind wir nun bei der Objektorientierung angelangt, und dahinter zeichnet sich bisher kein revolutionäres Programmierparadigma ab. Die Softwarekrise hat zu neuen Konzepten geführt, doch merkt fast jedes Entwicklungsteam, dass OO nicht alles ist, sondern nur ein verwunderter Ausspruch nach drei Jahren Entwicklungsarbeit an einem schönen Finanzprogramm: »Oh, oh, alles Mist.« So schön OO auch ist – wenn sich 10.000 Klassen im Klassendiagramm tummeln, ist das genauso unübersichtlich wie ein Fortran-Programm mit 10.000 Zeilen. Da in der Vergangenheit oft gutes Design für ein paar Millisekunden Laufzeit geopfert wurde, ist es nicht verwunderlich, dass Programme nicht mehr lesbar sind. Doch wie am Beispiel des Satzprogramms TeX (etwa 1985) zu sehen ist: Code lebt länger als Hardware, und die nächste Generation von Mehrkernprozessoren wird sich bald in unseren Desktop-PCs nach Arbeit sehnen.

Es fehlt demnach eine Ebene über den einzelnen Klassen und Objekten, denn die Objekte selbst sind nicht das Problem, vielmehr ist es die Kopplung. Hier helfen Regeln weiter, die unter dem Stichwort Entwurfsmuster (engl. design patterns) bekannt geworden sind. Dies sind Tipps von Softwaredesignern, denen aufgefallen war, dass viele Probleme auf ähnliche Weise gelöst werden können. Sie haben daher Regelwerke mit Lösungsmustern aufgestellt, die eine optimale Wiederverwendung von Bausteinen und Änderungsfreundlichkeit aufweisen. Design-Patterns ziehen sich durch die ganze Java-Klassenbibliothek, und die bekanntesten sind das Beobachter-(Observer-)Pattern, Singleton, Fabrik (Factory) und Composite.

 

Zum Seitenanfang

13.2.2    Singleton Zur vorigen ÜberschriftZur nächsten Überschrift

Ein Singleton ist eine Klasse, von der es in einer Anwendung nur ein Exemplar gibt.[ 230 ](Pro Klassenlader, um das etwas genauer auszudrücken ) Nützlich ist das für Dinge, die es nur genau einmal in einer Applikation geben soll, und davon gibt es einige Beispiele:

  • Eine grafische Anwendung hat nur ein Fenster.

  • Eine Konsolenanwendung hat nur je einen Eingabe-/Ausgabestrom.

  • Alle Druckaufträge wandern in eine Drucker-Warteschlage.

Unbestreitbar ist, dass es einmalige Objekte gibt, variantenreich ist jedoch der Weg dahin. Im Prinzip lässt sich unterscheiden zwischen einem Ansatz, bei dem

  • sich ein Framework um den einmaligen Aufbau des Objekts kümmert und dann auf Anfrage das Objekt liefert oder

  • bei dem wir selbst in Java-Code ein Singleton realisieren.

Die bessere Lösung ist, ein Framework zu nutzen, namentlich CDI, Guice, Spring oder Java EE. Doch die Java SE enthält keines davon, weswegen wir zur Demonstration den expliziten Weg gehen.

Die technischen Realisierungen sind vielseitig; in Java bieten sich zur Realisierung von Singletons Aufzählungen (enum) und normale Klassen an. Im Folgenden wollen wir ein Szenario annehmen, bei dem eine Anwendung zentral auf Konfigurationsdaten zurückgreifen möchte.

Singletons über Aufzählungen

Einen guten Weg für Singletons bieten Aufzählungen – auf den ersten Blick scheint ein Aufzählungstyp nicht dafür gemacht, denn eine Aufzählung impliziert ja irgendwie mehr als ein Element. Doch die Eigenschaften von enum sind perfekt für ein Singleton, und die Bibliothek implementiert einige Tricks, um das Objekt auch möglichst nur einmal zu erzeugen, etwa dann, wenn die Aufzählung serialisiert über die Leitung geht. Die Idee dabei ist, genau ein Element anzubieten (häufig INSTANCE genannt), das letztendlich ein Exemplar der Aufzählungsklasse wird, sowie die normalen Methoden:

Listing 13.1    com/tutego/insel/pattern/singleton/Configuration.java, Configuration

public enum Configuration {

INSTANCE;

private Properties props = new Properties( System.getProperties() );

public String getVersion() {

return "1.2";

}



public String getUserDir() {

return props.getProperty( "user.dir" );

}

}

Der Typ Configuration deklariert neben der später öffentlichen statischen Variable INSTANCE auch noch eine interne Variable props, die von der Aufzählung genutzt werden kann, um dort Zustände abzulegen oder zu erfragen. Wir machen das im Beispiel nur lesend über getUserDir().

Ein Nutzer greift wie üblich auf die enum-Eigenschaften zu:

Listing 13.2    com/tutego/insel/pattern/singleton/ConfigurationDemo.java, main()

System.out.println( Configuration.INSTANCE.getVersion() );  // 1.2

System.out.println( Configuration.INSTANCE.getUserDir() ); // C:\Users\...
 

Zum Seitenanfang

13.2.3    Fabrikmethoden Zur vorigen ÜberschriftZur nächsten Überschrift

Eine Fabrikmethode geht noch einen Schritt weiter als ein Singleton. Sie erzeugt nicht exakt ein Exemplar, sondern unter Umständen auch mehrere. Die grundlegende Idee ist jedoch, dass der Anwender nicht über einen Konstruktor ein Exemplar erzeugt, sondern im Allgemeinen über eine statische Methode. Dies hat den Vorteil, dass die statische Fabrikmethode zum Beispiel

  • alte Objekte aus einem Cache wiedergeben kann,

  • den Erzeugungsprozess auf Unterklassen verschieben kann und

  • null zurückgeben darf.

Ein Konstruktor erzeugt immer ein Exemplar der eigenen Klasse. Eine Rückgabe wie null kann ein Konstruktor nicht liefern, denn bei new wird immer ein neues Objekt gebaut. Fehler könnten nur über eine Exception angezeigt werden.

In der Java-Bibliothek gibt es eine Unmenge an Beispielen für Fabrikmethoden. Durch eine Namenskonvention sind sie leicht zu erkennen: Meistens heißen sie getInstance(). Eine Suche in der API-Dokumentation fördert gleich 90 solcher Methoden zutage. Viele sind parametrisiert, um genau anzugeben, was die Fabrik für Objekte erzeugen soll. Nehmen wir zum Beispiel die statischen Fabrikmethoden von java.util.Calendar:

  • Calendar.getInstance()

  • Calendar.getInstance( java.util.Locale )

  • Calendar.getInstance( java.util.TimeZone )

Die nicht parametrisierte Methode gibt ein Standard-Calendar-Objekt zurück. Calendar ist aber selbst eine abstrakte Basisklasse. Innerhalb der getInstance(…)-Methode befindet sich Quellcode wie der folgende:

Listing 13.3    java.util.Calender, getInstance()

static Calendar getInstance() {

...

return new GregorianCalendar();

...

}

Im Rumpf der Erzeugermethode getInstance(…) wird bewusst die Unterklasse GregorianCalendar ausgewählt, die Calendar erweitert. Das ist möglich, da durch Vererbung eine Ist-eine-Art-von-Beziehung gilt und GregorianCalendar ein Calendar ist. Der Aufrufer von getInstance(…) bekommt das nicht mit, und er empfängt wie gewünscht ein Calendar-Objekt. Mit dieser Möglichkeit kann getInstance(…) testen, in welchem Land die JVM läuft, und abhängig davon die passende Calendar-Implementierung auswählen.

 

Zum Seitenanfang

13.2.4    Das Beobachter-Pattern mit Listener realisieren Zur vorigen ÜberschriftZur nächsten Überschrift

Wir wollen uns nun mit dem Observer-Pattern beschäftigen, das seine Ursprünge in Smalltalk-80 hat. Dort ist es etwas erweitert unter dem Namen MVC (Model-View-Controller) bekannt. Mit diesem Muster müssen wir uns noch näher beschäftigen, da es ein ganz wesentliches Konzept bei der Programmierung grafischer Benutzeroberflächen mit Swing ist.

Eine Implementierung des Beobachter-Musters erlauben Listener. Es gibt Ereignisauslöser, die spezielle Ereignisobjekte aussenden, und Interessenten, die sich bei den Auslösern an- und abmelden. Die beteiligten Klassen und Schnittstellen folgen einer bestimmten Namenskonvention; XXX steht im Folgenden stellvertretend für einen Ereignisnamen, wie Window, Click …:

  • Eine Klasse für die Ereignisobjekte heißt XXXEvent. Die Ereignisobjekte können Informationen wie Auslöser, Zeitstempel und weitere Daten speichern.

  • Die Interessenten implementieren als Listener eine Java-Schnittstelle, die XXXListener heißt. Die Operation der Schnittstelle kann beliebig lauten, doch wird ihr üblicherweise das XXXEvent übergeben. Diese Schnittstelle kann auch mehrere Operationen vorschreiben.

  • Der Ereignisauslöser bietet die Methoden addXXXListener(XXXListener) und removeXXXListener(XXXListener) an, um Interessenten an- und abzumelden. Immer dann, wenn ein Ereignis stattfindet, erzeugt der Auslöser das Ereignisobjekt XXXEvent und informiert jeden Listener, der in der Liste eingetragen ist, über einen Aufruf der Methode aus dem Listener.

Ein Beispiel soll die beteiligten Typen verdeutlichen.

Radios spielen Werbung

Ein Radio soll für Werbung AdEvent-Objekte aussenden. Die Ereignisobjekte sollen den Werbespruch (Slogan) speichern:

Listing 13.4    com/tutego/insel/pattern/listener/AdEvent.java

package com.tutego.insel.pattern.listener;



import java.util.EventObject;

public class AdEvent extends EventObject {



private String slogan;



public AdEvent( Object source, String slogan ) {

super( source );

this.slogan = slogan;

}

public String getSlogan() {

return slogan;

}

}

Die Klasse AdEvent erweitert die Java-Basisklasse EventObject, eine Klasse, die traditionell alle Ereignisklassen erweitern. Der parametrisierte Konstruktor von AdEvent nimmt im ersten Parameter den Ereignisauslöser an und gibt ihn mit super(source) an den Konstruktor der Oberklasse weiter, der ihn speichert und mit getSource() wieder verfügbar macht. Zwingend ist der Einsatz der Basisklasse nicht unbedingt. Sie wurde auch nicht im Laufe der Jahre generisch angepasst, sodass source lediglich Object ist.

Der zweite Parameter des AdEvent-Konstruktors ist unsere Werbung.

Der AdListener ist die Schnittstelle, die Interessenten implementieren:

Listing 13.5    com/tutego/insel/pattern/listener/AdListener.java

package com.tutego.insel.pattern.listener;



import java.util.EventListener;



interface AdListener extends EventListener {

void advertisement( AdEvent e );

}

Unser AdListener implementiert die Schnittstelle EventListener (eine Markierungsschnittstelle), die alle Java-Listener implementieren sollen. Wir schreiben für konkrete Listener nur eine Operation advertisement(AdEvent) vor. Ob die Schnittstelle @FunctionalInterface tragen soll, muss sorgsam abgewogen werden, denn es wäre ja möglich, dass in Zukunft der Ereignisauslöser eine weitere Methode aufrufen möchte, um zum Beispiel etwas anderes zu melden.

UML-Klassendiagramm von »AdListener«, das »AdEvent« referenziert

Abbildung 13.1    UML-Klassendiagramm von »AdListener«, das »AdEvent« referenziert

[»]  Bemerkung

Falls wir die Callback-Methode nicht besonders benennen möchten, lässt sich auch eine allgemeine Listener-Schnittstelle der folgenden Art einsetzen:

public interface Listener<T extends Object, U extends Object> {

void notifyObserver( T source, U data );

}

Das Radio soll nun Interessenten an- und abmelden können. Es sendet über einen Timer Werbenachrichten. Das Spannende an der Implementierung ist die Tatsache, dass die Listener nicht in einer eigenen Datenstruktur verwaltet werden, sondern dass eine spezielle Listener-Klasse aus dem Swing-Paket verwendet wird:

Listing 13.6    com/tutego/insel/pattern/listener/Radio.java

package com.tutego.insel.pattern.listener;



import java.util.*;

import javax.swing.event.EventListenerList;



public class Radio {



private EventListenerList listeners = new EventListenerList();



private List<String> ads = Arrays.asList( "Jetzt explodiert auch der Haarknoten",

"Red Fish verleiht Flossen",

"Bom Chia Wowo",

"Wunder Whip. Iss milder." );



public Radio() {

new Timer().schedule( new TimerTask() {

@Override public void run() {

Collections.shuffle( ads );

notifyAdvertisement( new AdEvent( this, ads.get(0) ) );

}

}, 0, 500 );

}



public void addAdListener( AdListener listener ) {

listeners.add( AdListener.class, listener );

}



public void removeAdListener( AdListener listener ) {

listeners.remove( AdListener.class, listener );

}



protected synchronized void notifyAdvertisement( AdEvent event ) {

for ( AdListener l : listeners.getListeners( AdListener.class ) )

l.advertisement( event );

}

}

Die Demo-Anwendung nutzt das Radio-Objekt und implementiert einen konkreten Listener, einmal über eine innere anonyme Klasse und einmal über einen Lambda-Ausdruck:

Listing 13.7    com/tutego/insel/pattern/listener/RadioDemo.java, main()

Radio r = new Radio();

class ComplainingAdListener implements AdListener {

@Override public void advertisement( AdEvent e ) {

System.out.println( "Oh nein, schon wieder Werbung: " + e.getSlogan() );

}

}

r.addAdListener( new ComplainingAdListener() );

r.addAdListener( e -> System.out.println( "Ich höre nichts" ) );

Die Java-API-Dokumentation enthält einige generische Typen:

class javax.swing.event.EventListenerList
  • EventListenerList()

    Erzeugt einen Container für Horcher.

  • <T extends EventListener> void add(Class<T> t, T l)

    Fügt einen Listener l vom Typ T hinzu.

  • Object[] getListenerList()

    Liefert ein Array aller Listener.

  • <T extends EventListener> T[] getListeners(Class<T> t)

    Liefert ein Array aller Listener vom Typ t.

  • int getListenerCount()

    Nennt die Anzahl aller Listener.

  • int getListenerCount(Class<?> t)

    Nennt die Anzahl der Listener vom Typ t.

  • <T extends EventListener> void remove(Class<T> t, T l)

    Entfernt den Listener l aus der Liste.

 


Ihre Meinung?

Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de

<< zurück
 Zum Rheinwerk-Shop
Zum Rheinwerk-Shop: Java ist auch eine Insel Java ist auch eine Insel

Jetzt Buch bestellen


 Buchempfehlungen
Zum Rheinwerk-Shop: Captain CiaoCiao erobert Java

Captain CiaoCiao erobert Java




Zum Rheinwerk-Shop: Java SE 9 Standard-Bibliothek

Java SE 9 Standard-Bibliothek




Zum Rheinwerk-Shop: Algorithmen in Java

Algorithmen in Java




Zum Rheinwerk-Shop: Objektorientierte Programmierung

Objektorientierte Programmierung




 Lieferung
Versandkostenfrei bestellen in Deutschland, Österreich und in die Schweiz

InfoInfo



 

 


Copyright © Rheinwerk Verlag GmbH 2021

Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das Openbook denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt.

Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.

 

[Rheinwerk Computing]



Rheinwerk Verlag GmbH, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, service@rheinwerk-verlag.de



Cookie-Einstellungen ändern