From 33613a85afc4b1481367fbe92a17ee59c240250b Mon Sep 17 00:00:00 2001
From: Sven Eisenhauer
+Wir wollen uns in diesem Abschnitt damit beschäftigen, die oben
+erwähnten Entwurfsmuster für die Abwicklung des Nachrichtenverkehrs
+in Java-Programmen vorzustellen. Wie bereits erwähnt, hat jedes
+dieser Verfahren seine ganz spezifischen Vor- und Nachteile und ist
+für verschiedene Programmieraufgaben unterschiedlich gut geeignet.
+
+
+Als Basis für unsere Experimente wollen wir ein einfaches Programm
+schreiben, das die folgenden Anforderungen erfüllt:
+
+Basis der Programme ist das folgende Listing:
+
+
+
+
+
+
+ Titel
+ Inhalt
+ Suchen
+ Index
+ DOC
+ Handbuch der Java-Programmierung, 5. Auflage
+
+ <<
+ <
+ >
+ >>
+ API
+ Kapitel 28 - Event-Handling
+
+
+
+
+
+28.2 Entwurfsmuster für den Nachrichtenverkehr
+
+
+
+
+
+
+
+
+
+
+Listing 28.1: Basisprogramm für den Nachrichtentransfer
+
+
+
+
+
+001 /* Listing2801.java */
+002
+003 import java.awt.*;
+004 import java.awt.event.*;
+005
+006 public class Listing2801
+007 extends Frame
+008 {
+009 public static void main(String[] args)
+010 {
+011 Listing2801 wnd = new Listing2801();
+012 }
+013
+014 public Listing2801()
+015 {
+016 super("Nachrichtentransfer");
+017 setBackground(Color.lightGray);
+018 setSize(300,200);
+019 setLocation(200,100);
+020 setVisible(true);
+021 }
+022
+023 public void paint(Graphics g)
+024 {
+025 g.setFont(new Font("Serif",Font.PLAIN,18));
+026 g.drawString("Zum Beenden bitte ESC drücken...",10,50);
+027 }
+028 }
+
+
+Listing2801.java
+
+
![]() |
+
+
+ +Das Programm erfüllt die ersten der obengenannten Anforderungen, +ist aber mangels Event-Handler noch nicht in der Lage, per [ESC] +beendet zu werden. Um die Nachfolgeversionen vorzubereiten, haben +wir bereits die Anweisung import java.awt.event.* +eingefügt. |
+
+
|
+![]() |
+
+Die Ausgabe des Programms ist: +
+ +
+Abbildung 28.3: Das Programm für den Nachrichtentransfer
+ + + + ++Bei der ersten Variante gibt es nur eine einzige Klasse, Listing2802. +Sie ist einerseits eine Ableitung der Klasse Frame, +um ein Fenster auf dem Bildschirm darzustellen und zu beschriften. +Andererseits implementiert sie das Interface KeyListener, +das die Methoden keyPressed, +keyReleased +und keyTyped +definiert. Der eigentliche Code zur Reaktion auf die Taste [ESC] +steckt in der Methode keyPressed, +die immer dann aufgerufen wird, wenn eine Taste gedrückt wurde. +Mit der Methode getKeyCode +der Klasse KeyEvent +wird auf den Code der gedrückten Taste zugegriffen und dieser +mit der symbolischen Konstante VK_ESCAPE +verglichen. Stimmen beide überein, wurde [ESC] +gedrückt, und das Programm kann beendet werden. + + +
+
+
+
+001 /* Listing2802.java */
+002
+003 import java.awt.*;
+004 import java.awt.event.*;
+005
+006 public class Listing2802
+007 extends Frame
+008 implements KeyListener
+009 {
+010 public static void main(String[] args)
+011 {
+012 Listing2802 wnd = new Listing2802();
+013 }
+014
+015 public Listing2802()
+016 {
+017 super("Nachrichtentransfer");
+018 setBackground(Color.lightGray);
+019 setSize(300,200);
+020 setLocation(200,100);
+021 setVisible(true);
+022 addKeyListener(this);
+023 }
+024
+025 public void paint(Graphics g)
+026 {
+027 g.setFont(new Font("Serif",Font.PLAIN,18));
+028 g.drawString("Zum Beenden bitte ESC drücken...",10,50);
+029 }
+030
+031 public void keyPressed(KeyEvent event)
+032 {
+033 if (event.getKeyCode() == KeyEvent.VK_ESCAPE) {
+034 setVisible(false);
+035 dispose();
+036 System.exit(0);
+037 }
+038 }
+039
+040 public void keyReleased(KeyEvent event)
+041 {
+042 }
+043
+044 public void keyTyped(KeyEvent event)
+045 {
+046 }
+047 }
+
+ |
++Listing2802.java | +
+Die Verbindung zwischen der Ereignisquelle (in diesem Fall der Fensterklasse +Listing2802) und dem Ereignisempfänger +(ebenfalls die Klasse Listing2802) +erfolgt über den Aufruf der Methode addKeyListener +der Klasse Frame. +Alle Tastaturereignisse werden dadurch an die Fensterklasse selbst +weitergeleitet und führen zum Aufruf der Methoden keyPressed, +keyReleased +oder keyTyped +des Interfaces KeyListener. +Diese Implementierung ist sehr naheliegend, denn sie ist einfach zu +implementieren und erfordert keine weiteren Klassen. Nachteilig ist +dabei allerdings: +
+Es bleibt festzuhalten, dass diese Technik bestenfalls für kleine +Programme geeignet ist, die nur begrenzt erweitert werden müssen. +Durch die Vielzahl leerer Methodenrümpfe können aber auch +kleine Programme schnell unübersichtlich werden. + + + + +
+Die zweite Alternative bietet eine bessere Lösung. Sie basiert +auf der Verwendung lokaler bzw. anonymer Klassen und +kommt ohne die Nachteile der vorigen Version aus. Sie ist das in der +Dokumentation des JDK empfohlene Entwurfsmuster für das Event-Handling +in kleinen Programmen oder bei Komponenten mit einfacher Nachrichtenstruktur. +Vor ihrem Einsatz sollte man allerdings das Prinzip lokaler und anonymer +Klassen kennenlernen, das mit dem JDK 1.1 in Java eingeführt +und in Abschnitt 10.1 +vorgestellt wurde. Wer diesen Abschnitt noch nicht gelesen hat, sollte +das jetzt nachholen. + + + + +
+Die Anwendung lokaler Klassen für die Ereignisbehandlung besteht +darin, mit ihrer Hilfe die benötigten EventListener +zu implementieren. Dazu wird in dem GUI-Objekt, das einen Event-Handler +benötigt, eine lokale Klasse definiert und aus einer passenden +Adapterklasse abgeleitet. Nun braucht nicht mehr das gesamte +Interface implementiert zu werden (denn die Methodenrümpfe werden +ja aus der Adapterklasse geerbt), sondern lediglich die tatsächlich +benötigten Methoden. Da die lokale Klasse zudem auf die Membervariablen +und Methoden der Klasse zugreifen kann, in der sie definiert wurde, +lassen sich auf diese Weise sehr schnell die benötigten Ereignisempfänger +zusammenbauen. + +
+Das folgende Beispiel definiert eine lokale Klasse MyKeyListener, +die aus KeyAdapter +abgeleitet wurde und auf diese Weise das KeyListener-Interface +implementiert. Sie überlagert lediglich die Methode keyPressed, +um auf das Drücken einer Taste zu reagieren. Als lokale Klasse +hat sie außerdem Zugriff auf die Methoden der umgebenden Klasse +und kann somit durch Aufruf von setVisible +und dispose +das Fenster, in dem sie als Ereignisempfänger registriert wurde, +schließen. Die Registrierung der lokalen Klasse erfolgt durch +Aufruf von addKeyListener, +bei dem gleichzeitig eine Instanz der lokalen Klasse erzeugt wird. +Als lokale Klasse ist MyKeyListener +überall innerhalb von Listing2803 +sichtbar und kann an beliebiger Stelle instanziert werden. + + +
+
+
+
+001 /* Listing2803.java */
+002
+003 import java.awt.*;
+004 import java.awt.event.*;
+005
+006 public class Listing2803
+007 extends Frame
+008 {
+009 public static void main(String[] args)
+010 {
+011 Listing2803 wnd = new Listing2803();
+012 }
+013
+014 public Listing2803()
+015 {
+016 super("Nachrichtentransfer");
+017 setBackground(Color.lightGray);
+018 setSize(300,200);
+019 setLocation(200,100);
+020 setVisible(true);
+021 addKeyListener(new MyKeyListener());
+022 }
+023
+024 public void paint(Graphics g)
+025 {
+026 g.setFont(new Font("Serif",Font.PLAIN,18));
+027 g.drawString("Zum Beenden bitte ESC drücken...",10,50);
+028 }
+029
+030 class MyKeyListener
+031 extends KeyAdapter
+032 {
+033 public void keyPressed(KeyEvent event)
+034 {
+035 if (event.getKeyCode() == KeyEvent.VK_ESCAPE) {
+036 setVisible(false);
+037 dispose();
+038 System.exit(0);
+039 }
+040 }
+041 }
+042 }
+
+ |
++Listing2803.java | +
+Der Vorteil dieser Vorgehensweise ist offensichtlich: es werden keine +unnützen Methodenrümpfe erzeugt, aber trotzdem verbleibt +der Ereignisempfängercode wie im vorigen Beispiel innerhalb der +Ereignisquelle. Dieses Verfahren ist also immer dann gut geeignet, +wenn es von der Architektur oder der Komplexität der Ereignisbehandlung +her sinnvoll ist, Quelle und Empfänger zusammenzufassen. + + + + +
+Das folgende Beispiel ist eine leichte Variation des vorigen. Es zeigt +die Verwendung einer anonymen Klasse, die aus KeyAdapter +abgeleitet wurde, als Ereignisempfänger. Zum Instanzierungszeitpunkt +erfolgt die Definition der überlagernden Methode keyPressed, +in der der Code zur Reaktion auf das Drücken der Taste [ESC] +untergebracht wird. + + +
+
+
+
+001 /* Listing2804.java */
+002
+003 import java.awt.*;
+004 import java.awt.event.*;
+005
+006 public class Listing2804
+007 extends Frame
+008 {
+009 public static void main(String[] args)
+010 {
+011 Listing2804 wnd = new Listing2804();
+012 }
+013
+014 public Listing2804()
+015 {
+016 super("Nachrichtentransfer");
+017 setBackground(Color.lightGray);
+018 setSize(300,200);
+019 setLocation(200,100);
+020 setVisible(true);
+021 addKeyListener(
+022 new KeyAdapter() {
+023 public void keyPressed(KeyEvent event)
+024 {
+025 if (event.getKeyCode() == KeyEvent.VK_ESCAPE) {
+026 setVisible(false);
+027 dispose();
+028 System.exit(0);
+029 }
+030 }
+031 }
+032 );
+033 }
+034
+035 public void paint(Graphics g)
+036 {
+037 g.setFont(new Font("Serif",Font.PLAIN,18));
+038 g.drawString("Zum Beenden bitte ESC drücken...",10,50);
+039 }
+040 }
+
+ |
++Listing2804.java | +
+Vorteilhaft bei dieser Vorgehensweise ist der verminderte Aufwand, +denn es muss keine separate Klassendefinition angelegt werden. Statt +dessen werden die wenigen Codezeilen, die zur Anpassung der Adapterklasse +erforderlich sind, dort eingefügt, wo die Klasse instanziert +wird, nämlich beim Registrieren des Nachrichtenempfängers. +Anonyme Klassen haben einen ähnlichen Einsatzbereich wie lokale, +empfehlen sich aber vor allem, wenn sehr wenig Code für den Ereignisempfänger +benötigt wird. Bei aufwändigeren Ereignisempfängern +ist die explizite Definition einer benannten Klasse dagegen vorzuziehen. + + + + +
+Wir hatten am Anfang darauf hingewiesen, dass in größeren +Programmen eine Trennung zwischen Programmcode, der für die Oberfläche +zuständig ist, und solchem, der für die Anwendungslogik +zuständig ist, wünschenswert wäre. Dadurch wird eine +bessere Modularisierung des Programms erreicht, und der Austausch +oder die Erweiterung von Teilen des Programms wird erleichtert. +
+
![]() |
+
+
+ +Das Delegation Event Model wurde auch mit dem Designziel entworfen, +eine solche Trennung zu ermöglichen bzw. zu erleichtern. Der +Grundgedanke dabei war es, auch Nicht-Komponenten die Reaktion auf +GUI-Events zu ermöglichen. Dies wurde dadurch erreicht, dass +jede Art von Objekt als Ereignisempfänger registriert werden +kann, solange es die erforderlichen Listener-Interfaces implementiert. +Damit ist es möglich, die Anwendungslogik vollkommen von der +grafischen Oberfläche abzulösen und in Klassen zu verlagern, +die eigens für diesen Zweck entworfen wurden. |
+
+
|
+![]() |
+
+Das nachfolgende Beispiel zeigt diese Vorgehensweise, indem es unser +Beispielprogramm in die drei Klassen Listing2805, +MainFrameCommand und MainFrameGUI +aufteilt. Listing2805 enthält +nur noch die main-Methode +und dient lediglich dazu, die anderen beiden Klassen zu instanzieren. +MainFrameGUI realisiert die +GUI-Funktionalität und stellt das Fenster auf dem Bildschirm +dar. MainFrameCommand spielt +die Rolle des Kommandointerpreters, der immer dann aufgerufen wird, +wenn im Fenster ein Tastaturereignis aufgetreten ist. + +
+Die Verbindung zwischen beiden Klassen erfolgt durch Aufruf der Methode +addKeyListener +in MainFrameGUI, an die das +an den Konstruktor übergebene MainFrameCommand-Objekt +weitergereicht wird. Dazu ist es erforderlich, dass das Hauptprogramm +den Ereignisempfänger cmd +zuerst instanziert, um ihn bei der Instanzierung des GUI-Objekts gui +übergeben zu können. +
+
![]() |
+![]() |
+
+
+ +Umgekehrt benötigt natürlich auch das Kommando-Objekt Kenntnis +über das GUI-Objekt, denn es soll ja das zugeordnete Fenster +schließen und das Programm beenden. Der scheinbare Instanzierungskonflikt +durch diese zirkuläre Beziehung ist aber in Wirklichkeit gar +keiner, denn bei jedem Aufruf einer der Methoden von MainFrameCommand +wird an das KeyEvent-Objekt +der Auslöser der Nachricht übergeben, und das ist in diesem +Fall stets das MainFrameGUI-Objekt +gui. So kann innerhalb des Kommando-Objekts +auf alle öffentlichen Methoden des GUI-Objekts zugegriffen werden. |
+
+
|
+![]() |
+
+
+
+
+001 /* Listing2805.java */
+002
+003 import java.awt.*;
+004 import java.awt.event.*;
+005
+006 public class Listing2805
+007 {
+008 public static void main(String[] args)
+009 {
+010 MainFrameCommand cmd = new MainFrameCommand();
+011 MainFrameGUI gui = new MainFrameGUI(cmd);
+012 }
+013 }
+014
+015 class MainFrameGUI
+016 extends Frame
+017 {
+018 public MainFrameGUI(KeyListener cmd)
+019 {
+020 super("Nachrichtentransfer");
+021 setBackground(Color.lightGray);
+022 setSize(300,200);
+023 setLocation(200,100);
+024 setVisible(true);
+025 addKeyListener(cmd);
+026 }
+027
+028 public void paint(Graphics g)
+029 {
+030 g.setFont(new Font("Serif",Font.PLAIN,18));
+031 g.drawString("Zum Beenden bitte ESC drücken...",10,50);
+032 }
+033 }
+034
+035 class MainFrameCommand
+036 implements KeyListener
+037 {
+038 public void keyPressed(KeyEvent event)
+039 {
+040 Frame source = (Frame)event.getSource();
+041 if (event.getKeyCode() == KeyEvent.VK_ESCAPE) {
+042 source.setVisible(false);
+043 source.dispose();
+044 System.exit(0);
+045 }
+046 }
+047
+048 public void keyReleased(KeyEvent event)
+049 {
+050 }
+051
+052 public void keyTyped(KeyEvent event)
+053 {
+054 }
+055 }
+
+ |
++Listing2805.java | +
+Diese Designvariante ist vorwiegend für größere Programme +geeignet, bei denen eine Trennung von Programmlogik und Oberfläche +sinnvoll ist. Für sehr kleine Programme oder solche, die wenig +Ereigniscode haben, sollte eher eine der vorherigen Varianten angewendet +werden, wenn diese zu aufwändig ist. Sie entspricht in groben +Zügen dem Mediator-Pattern, das +in »Design-Patterns« von Gamma et al. beschrieben wird. + +
+Natürlich erhebt das vorliegende Beispielprogramm nicht den Anspruch, +unverändert in ein sehr großes Programm übernommen +zu werden. Es soll lediglich die Möglichkeit der Trennung von +Programmlogik und Oberfläche in einem großen Programm mit +Hilfe der durch das Event-Handling des JDK 1.1 vorgegebenen Möglichkeiten +aufzeigen. Eine sinnvolle Erweiterung dieses Konzepts könnte +darin bestehen, weitere Modularisierungen vorzunehmen (z.B. analog +dem MVC-Konzept von Smalltalk, bei +dem GUI-Anwendungen in Model-, View- und Controller-Layer +aufgesplittet werden, oder auch durch Abtrennen spezialisierter Kommandoklassen). +Empfehlenswert ist in diesem Zusammenhang die Lektüre der JDK-Dokumentation, +die ein ähnliches Beispiel in leicht veränderter Form enthält. + + + + +
+Als letzte Möglichkeit, auf Nachrichten zu reagieren, soll das +Überlagern der Event-Handler in den Ereignisquellen selbst aufgezeigt +werden. Jede Ereignisquelle besitzt eine Reihe von Methoden, die für +das Aufbereiten und Verteilen der Nachrichten zuständig sind. +Soll eine Nachricht weitergereicht werden, so wird dazu zunächst +innerhalb der Nachrichtenquelle die Methode processEvent +aufgerufen. Diese verteilt die Nachricht anhand ihres Typs an spezialisierte +Methoden, deren Name sich nach dem Typ der zugehörigen Ereignisklasse +richtet. So ist beispielsweise die Methode processActionEvent +für das Handling von Action-Events und processMouseEvent +für das Handling von Mouse-Events zuständig: +
+
+
++protected void processEvent(AWTEvent e) + +protected void processComponentEvent(ComponentEvent e) + +protected void processFocusEvent(FocusEvent e) + +... ++ + |
++java.awt.Component | +
+
![]() |
+![]() |
+
+
+ +Beide Methodenarten können in einer abgeleiteten Klasse überlagert +werden, um die zugehörigen Ereignisempfänger zu implementieren. +Wichtig ist dabei, dass in der abgeleiteten Klasse die gleichnamige +Methode der Basisklasse aufgerufen wird, um das Standardverhalten +sicherzustellen. Wichtig ist weiterhin, dass sowohl processEvent +als auch processActionEvent +usw. nur aufgerufen werden, wenn der entsprechende Ereignistyp für +diese Ereignisquelle aktiviert wurde. Dies passiert in folgenden Fällen: +
|
+
+
|
+![]() |
+
+Die Methode enableEvents +erwartet als Argument eine Maske, die durch eine bitweise Oder-Verknüpfung +der passenden Maskenkonstanten aus der Klasse AWTEvent +zusammengesetzt werden kann: + +
+
+
+
++protected final void enableEvents(long eventsToEnable) ++ + |
++java.awt.Component | +
+Die verfügbaren Masken sind analog zu den Ereignistypen benannt +und heißen ACTION_EVENT_MASK, +ADJUSTMENT_EVENT_MASK, +COMPONENT_EVENT_MASK +usw. + +
+Das folgende Beispiel überlagert die Methode processKeyEvent +in der Klasse Frame +(die sie aus Component +geerbt hat). Durch Aufruf von enableEvents +wird die Weiterleitung der Tastaturereignisse aktiviert, und das Programm +zeigt dasselbe Verhalten wie die vorigen Programme. + + +
+
+
+
+001 /* Listing2806.java */
+002
+003 import java.awt.*;
+004 import java.awt.event.*;
+005
+006 public class Listing2806
+007 extends Frame
+008 {
+009 public static void main(String[] args)
+010 {
+011 Listing2806 wnd = new Listing2806();
+012 }
+013
+014 public Listing2806()
+015 {
+016 super("Nachrichtentransfer");
+017 setBackground(Color.lightGray);
+018 setSize(300,200);
+019 setLocation(200,100);
+020 setVisible(true);
+021 enableEvents(AWTEvent.KEY_EVENT_MASK);
+022 }
+023
+024 public void paint(Graphics g)
+025 {
+026 g.setFont(new Font("Serif",Font.PLAIN,18));
+027 g.drawString("Zum Beenden bitte ESC drücken...",10,50);
+028 }
+029
+030 public void processKeyEvent(KeyEvent event)
+031 {
+032 if (event.getID() == KeyEvent.KEY_PRESSED) {
+033 if (event.getKeyCode() == KeyEvent.VK_ESCAPE) {
+034 setVisible(false);
+035 dispose();
+036 System.exit(0);
+037 }
+038 }
+039 super.processKeyEvent(event);
+040 }
+041 }
+
+ |
++Listing2806.java | +
+Diese Art der Ereignisbehandlung ist nur sinnvoll, wenn Fensterklassen +oder Dialogelemente überlagert werden und ihr Aussehen oder Verhalten +signifikant verändert wird. Alternativ könnte natürlich +auch in diesem Fall ein EventListener +implementiert und die entsprechenden Methoden im Konstruktor der abgeleiteten +Klasse registriert werden. +
+
![]() |
+
+
+ +Das hier vorgestellte Verfahren umgeht das Delegation Event Model +vollständig und hat damit die gleichen inhärenten Nachteile +wie das Event-Handling des alten JDK. Die Dokumentation zum JDK empfiehlt +daher ausdrücklich, für alle »normalen« Anwendungsfälle +das Delegation Event Model zu verwenden und die Anwendungen nach einem +der in den ersten drei Beispielen genannten Entwurfsmuster zu implementieren. |
+
+
|
+![]() |
+
+Die hier vorgestellten Entwurfsmuster geben einen Überblick über +die wichtigsten Designtechniken für das Event-Handling in Java-Programmen. +Während die ersten beiden Beispiele für kleine bis mittelgroße +Programme gut geeignet sind, kommen die Vorteile der in Variante 3 +vorgestellten Trennung zwischen GUI-Code und Anwendungslogik vor allem +bei größeren Programmen zum Tragen. Die vierte Variante +ist vornehmlich für Spezialfälle geeignet und sollte entsprechend +umsichtig eingesetzt werden. +
+
![]() |
+
+
+ +Wir werden in den nachfolgenden Kapiteln vorwiegend die ersten beiden +Varianten einsetzen. Wenn es darum geht, Ereignishandler für +die Beispielprogramme zu implementieren, werden wir also entweder +die erforderlichen Listener-Interfaces in der Fensterklasse selbst +implementieren oder sie in lokalen oder anonymen Klassen unterbringen. +Da das Event-Handling des JDK 1.1 eine Vielzahl von Designvarianten +erlaubt, werden wir uns nicht immer sklavisch an die vorgestellten +Entwurfsmuster halten, sondern teilweise leicht davon abweichen oder +Mischformen verwenden. Dies ist beabsichtigt und soll den möglichen +Formenreichtum demonstrieren. Wo nötig, werden wir auf spezielle +Implementierungsdetails gesondert eingehen. |
+
+
|
+![]() |
+
+Kapitel 29 widmet +sich den wichtigsten Low-Level-Events und demonstriert den genauen +Einsatz ihrer Listener- und Event-Methoden anhand vieler Beispiele. +In späteren Kapiteln werden die meisten der High-Level-Events +erläutert. Sie werden in der Regel dort eingeführt, wo ihr +Einsatz durch das korrespondierende Dialogelement motiviert wird. +So erläutert Kapitel 30 +in Zusammenhang mit der Vorstellung von Menüs die Action-Ereignisse, +und in Kapitel 32 +werden Ereignisse erläutert, die von den dort vorgestellten Dialogelementen +ausgelöst werden. +
| 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 + |