Ereignisbehandlung – 21 – 3 Ereignisbehandlung Dieses Kapitel beschäftigt sich mit der Ereignisbehandlung, d.h. der Reaktion eines Programms auf Eingaben durch benutzende Personen. Nach einigen ersten Beispielen hierzu aus dem Lehrbuch wird das Thema durch zwei Betrachtungen vertieft: • Zunächst wird gezeigt, wie die Ereignisbehandlung in Swing gemäß des Entwurfsmusters Observer (Beobachter) gestaltet ist. Sie werden erfahren, was Entwurfsmuster im Allgemeinen sind, was das Entwurfsmuster Beobachter ist und wie es mit Hilfe eines Klassendiagramms beschrieben werden kann. • Anschließend werden drei Möglichkeiten diskutiert, wie auf unterschiedliche Ereignisse unterschiedlich reagiert werden kann. Lesen Sie den Abschnitt 4.1.2 (S. 180-184) des Lehrbuchs. Auch hier wollen wir noch einmal zu Übungszwecken eines der Programme durch ein Klassendiagramm darstellen. Abbildung 3.1 zeigt das Diagramm zu dem Programm aus Listing 4.4. ActionListener JFrame ButtonExample1 benutzt MyHandler benutzt JButton Abbildung 3.1: Klassendiagramm zum Programm aus Listing 4.4 des Lehrbuchs 3.1 Entwurfsmuster Observer Für Programmierneulinge könnten die Programme aus den Listings 4.4, 4.5 und 4.6 ungewöhnlich sein: Es wird eine Methode actionPerformed definiert, die an keiner Stelle im selbst geschriebenen Programmcode aufgerufen wird, die aber bei Ausführung des Programms dennoch irgendwie aktiviert wird. Dieses Prinzip wird manchmal auch als das Hollywood-Prinzip bezeichnet: „Don’t call us – we call you“. Dies soll angeblich in Hollywood den Kandidatinnen und Kandidaten nach dem Vorsprechen für eine Filmrolle (Casting) mit auf den Heimweg gegeben werden. – 22 – Frameworks Entwurfsmuster Entwufsmuster Observer (Beobachter) Grundlagen der Programmierung grafischer Benutzeroberflächen in Java Dieses Prinzip ist ein Wesensmerkmal von so genannten Frameworks wie Swing. In einer Initialisierungsphase (in unserem Beispiel in der MainMethode) werden für unterschiedliche Ereignisse (in unserem Beispiel das Drücken eines Buttons) anwendungsspezifische Reaktionen angemeldet (in unserem Beispiel durch Aufruf der Methode addActionListener). Nach der Initialisierungsphase liegt die Steuerung des Programms nicht mehr in den Händen des Anwendungscodes, sondern des Frameworks. Das Framework verarbeitet die Ereignisse und ruft entsprechend den angemeldeten Reaktionen anwendungsspezifische Codeteile auf. Dieses Verhalten wird durch das Entwurfsmuster Observer (Beobachter) beschrieben. Entwurfsmuster sind auf Erfahrung basierende Vorschläge, wie bestimmte Aufgabenstellungen in Software umgesetzt werden können. Entwurfsmuster haben ein höheres Abstraktionsniveau als Klassenbibliotheken, da Entwurfsmuster keinen Programmcode darstellen, sondern nur beschreiben, wie Klassen und Schnittstellen in einer bestimmten Situation zusammenwirken sollen. Die konkreten Klassen und Schnittstellen mit ihren Methoden sind aber abhängig von der jeweiligen Anwendung. Die in der Beschreibung des Entwurfsmusters verwendeten Klassen und Schnittstellen sowie die verwendeten Methoden sind nur beispielhaft zu verstehen. Natürlich werden solche Entwurfsmuster mit Hilfe von Klassendiagrammen beschrieben. Das Klassendiagramm zum Entwurfsmuster Observer ist in Abbildung 3.2 abgebildet. Observable * Observer1 Observer Observer2 Abbildung 3.2: Klassendiagramm zum Entwurfsmuster Observer (Beobachter) Observer ist eine Schnittstelle mit mindestens einer Methode, die beim Eintritt eines bestimmten Ereignisses aufgerufen werden soll. Observer1 und Observer2 sind stellvertretend zwei Klassen, welche die Observer-Schnittstelle implementieren und entsprechend Implementierungen für die Methode(n) der Observer-Schnittstelle beinhalten. Observable ist eine Klasse, die sich beobachten lässt (d.h. etwas Beobachtbares). Dazu können mehrere Objekte des Typs Observer an Observable angemeldet werden, wobei sich das Observable-Objekt alle angemeldeten Objekte merken muss, da es diese bei späterem Eintreten des interessierenden Ereignisses benachrichtigen muss. Ereignisbehandlung Zur Verdeutlichung entwickeln wir dazu ein Beispiel. Als beobachtbares Objekt stellen wir uns einen Messfühler vor, der periodisch eine bestimmte Größe (Temperatur, Druck, Windgeschwindigkeit, Anzahl der vorbeifahrenden Autos pro Sekunde usw.) misst. An diesem Messfühler können sich Beobachter anmelden, die unterschiedlich auf die Meldung neuer Messwerte reagieren können (Wert in eine Log-Datei schreiben, eine grafische Darstellung der N letzten Werte aktualisieren, bei Über- bzw. Unterschreitung bestimmter Grenzwerte Alarm durch Senden einer SMS auslösen usw.). Alle Beobachter müssen die Schnittstelle MeasurementObserver und damit die Methode valueMeasured implementieren: interface MeasurementObserver { public void valueMeasured(double newValue); } class MeasurementDevice { private ArrayList<MeasurementObserver> observers; public MeasurementDevice() { observers = new ArrayList<MeasurementObserver>(); } public void addMeasurementObserver(MeasurementObserver mo) { observers.add(mo); } private void fireEvent(double newValue) { for(MeasurementObserver mo : observers) { mo.valueMeasured(newValue); } } public void doMeasuring() { for(int i = 0; i < 3; i++) { //random value between -100 and +100: double measuredValue = -100 + Math.random() * 200; fireEvent(measuredValue); } } } class LogObserver implements MeasurementObserver { public void valueMeasured(double newValue) { System.out.println("Eintrag in Log-Datei: " + newValue); } } – 23 – Messfühler als Beispiel für das Entwurfsmuster Beobachter – 24 – Grundlagen der Programmierung grafischer Benutzeroberflächen in Java class GraphicObserver implements MeasurementObserver { public void valueMeasured(double newValue) { System.out.println("Aktualisierung der Grafik mit " + "neuem Wert: " + newValue); } } class AlarmObserver implements MeasurementObserver { private double minValue; private double maxValue; public AlarmObserver(double minValue, double maxValue) { this.minValue = minValue; this.maxValue = maxValue; } public void valueMeasured(double newValue) { if(newValue > maxValue || newValue < minValue) { System.out.println("Alarm (z.B. als SMS oder " + "E-Mail) senden mit Wert: " + newValue); } } } public class DesignPatternObserverExample { public static void main(String[] args) { MeasurementDevice device = new MeasurementDevice(); LogObserver logger = new LogObserver(); device.addMeasurementObserver(logger); GraphicObserver graphics = new GraphicObserver(); device.addMeasurementObserver(graphics); AlarmObserver alarm = new AlarmObserver(-50, 50); device.addMeasurementObserver(alarm); device.doMeasuring(); } } Die aus MeasurementObserver abgeleiteten Klassen sind sozusagen die anwendungsspezifischen Codeteile, während der Rest das Mess-Framework darstellt. Ereignisbehandlung Zusammenfassung Die Swing-Interaktionselemente wie z.B. ein JButton können durch Aktionen der Benutzerinnen und Benutzer Ereignisse auslösen. Sie stellen so genannte Ereignisquellen dar. An diese Ereignisquellen können Objekte angemeldet werden, die abhängig von der Ereignisquelle eine bestimmte Schnittstelle implementieren müssen. Tritt das Ereignis ein, werden alle angemeldeten Objekte durch Aufruf einer der Methoden der implementierten Schnittstelle über das Eintreten des Ereignisses informiert. Das Grundprinzip dieses Zusammenwirkens wird im Entwurfsmuster Beobachter festgehalten. Die Ereignisquellen spielen hierbei die Rolle der Beobachtbaren, während die benachrichtigten Objekte Beobachter genannt werden. Ein vollständig selbst implementiertes Beispiel für dieses Zusammenspiel verdeutlicht das Entwurfsmuster Beobachter. " Übungsaufgaben 3.1 Welche Ausgabe kann das Beispielprogramm dieses Abschnitts erzeugen? Überlegen Sie zuerst und prüfen Sie Ihre Überlegungen dann durch Ausführung des Programms nach! 3.2 Zeichnen Sie ein Sequenzdiagramm für den Aufruf der Methode doMeasuring auf dem Objekt device in der Main-Methode! Nehmen Sie dabei der Einfachheit halber an, dass die Schleife in der Methode doMeasuring nur ein einziges Mal durchlaufen wird! Zeichnen Sie die Aufrufe der Methoden Math.random und System.out.println nicht in das Sequenzdiagramm ein! 3.2 Unterschiedliche Reaktionen auf unterschiedliche Ereignisse In den Beispielen des Abschnitts 4.1.2 aus dem Lehrbuch gibt es immer nur eine einzige Ereignisquelle. Wenn aber mehrere Ereignisquellen vorhanden sind (z.B. mehrere Buttons), dann will man in der Regel auf das Drücken der Buttons in unterschiedlicher Weise reagieren (sonst bräuchte man ja nur einen Button). Es gibt prinzipiell drei Möglichkeiten, mit diesem Problem umzugehen: – 25 –