[an error occurred while processing this directive]

Einführung in Ereignisse in Java im Rahmen der Lehre der Programmierung mit der Programmiersprache Java. [] (Delegation Event Model in Java, events, Ereignis, Ereignisse, delegation event modell), Lektion, Seite 721631
https://www.purl.org/stefan_ram/pub/ereignisse-java ist die kanonische URI dieser Seite.
Stefan Ram

Ereignisse in Java

Schnittstellen enthalten in Java  nur Anwendungstypen (also Signaturen und Ergebnistypen), aber keine Semantik. Die kann zwar durch Dokumentationskommentare angegeben werden, aber der Inhalt dieser Dokumentationskommentare ist nicht für den Java -Übersetzer sondern für Menschen (Programmierer) gedacht und wird daher vom Java -Übersetzer nicht überprüft oder ausgewertet.

Semantische Schärfe

Eine semantische Spezifikation  kann mehr oder weniger scharf  sein. Die Spezifikation der Operation "compareTo( Object )" in der Schnittstelle "Comparable" sagt beispielsweise, daß diese Operation ein positives Ergebnis  zurückgibt, wenn das Empfängerobjekt "e" der Versendung "e.compareTo( o )" größer  als das Argumentobjekt "o" ist. Die Spezifikation könnte auch schärfer  (genauer) sein, also weniger Möglichkeiten der Implementation offenlassen, indem sie verlangt, daß in diesem Fall genau der Wert "+1" und nicht irgendein  positiver Wert zurückgegeben wird. Andererseits könnte die Spezifikation auch unschärfer  sein und nur verlangen, daß die Versendung in diesem Fall einen beliebigen  numerischen Wert zurückgibt (was aber nicht mehr so nützlich wäre).

Höchstmögliche Schärfe ist nicht immer wünschenswert, die Schärfe soll vielmehr genau dosiert sein, um ein bestimmtes Ziel zu erreichen, so wie auch ein gutes Photo manchmal absichtlich unscharfe Stellen enthält oder ein Schriftsteller bestimmte Details offenläßt. Im Falle der Operation "compareTo( Object )" erlaubt die Unschärfe es, daß als Ergebnis für den erwähnten Fall als positive Zahl der Wert "+1" zurückgegeben wird, aber auch jede andere positive Zahl ist erlaubt und kann verwendet werden, um durch den Betrag des Ergebnisses noch eine zusätzliche Information über das Vergleichsergebnis zurückzugeben, die durch die Spezifikation der Schnittstelle "Comparable" nicht weiter eingeschränkt wird.

Ereignisse

Ein Ereignis ist ein Anwendungstyp zusammen mit einer Bedeutung, die aber etwas anderes festlegt, als die Bedeutung einer Operation. Das Ereignis ist weniger durch sein Verhalten  spezifiziert als dadurch, unter welchen Umständen  es verschickt wird. Dadurch ist es einem Ereignis-Implementierer weitgehend freigestellt, irgend etwas  zu machen, wenn er eine Ereignis-Nachricht erhält, und dadurch die Wirkung des Ereignisses weitgehend selber zu definieren. Insofern ist ein Ereignis als Operation abstrakt, aber hinsichtlich der Umstände, unter denen es auftritt, konkret.

Ereignisse und Operationen
Ereignis:  Sender ----------------> Empfänger

           Festgelegt, unter        Nicht festgelegt,
welchen Umständen welches Verhalten Operation: Sender ----------------> Empfänger Nicht festgelegt, Festgelegt,
unter welchen welches Verhalten
Umständen

Ein Ereignis kann verglichen werden mit einem Klangsignal eines Fernsprechapparates. Dieses hat keine allgemein festgelegte Wirkung. Der Besitzer eines Fernsprechapparates kann darauf reagieren, wie er will. Der Absender des Klangsignals (Anrufer) kann dadurch nicht eine bestimmte Wirkung hervorrufen, sondern muß akzeptieren, was immer der Besitzer des Apparates entscheidet.

Im Falle eines Ereignisses ist also die Versendung von einer bestimmten Wirkung entkoppelt. Das Ereignis kann versendet werden, und welche Wirkung es hat, bestimmt der Empfänger. Wenn der Absender weiß, wie der Empfänger reagiert, dann kann er natürlich mit einer bestimmten Wirkung rechnen. Während man im allgemeinen nicht weiß, ob ein Anruf entgegengenommen wird, kann man bei einem Notrufdienst der Polizei damit rechnen.

Für Ereignisse ist es relativ scharf spezifiziert, unter welchen Umständen sie versendet werden, insofern liegt die Pflicht zur Erfüllung der Spezifikation zunächst beim Absender des Ereignisses, der dafür verantwortlich ist, das Ereignis wie spezifiziert zu versenden. So könnte es beispielsweise spezifiziert sein, daß ein bestimmtes Ereignis immer dann verschickt wird, wenn eine Taste angetippt wurde. Dann ist der Ereignissender dadurch in der Pflicht, diese Spezifikation zu erfüllen, während es dem Ereignisempfänger weitgehend freigestellt ist, wie er auf diese Nachricht reagiert.

Für ein Ereignis ist spezifiziert, unter welchen Umständen der Sender dieses versenden muß, während es dem Empfänger weitgehend freigestellt ist, wie er darauf reagiert.

Ein Ereignis modelliert den Eintritt eines Sachverhaltes, also daß  irgend etwas geschieht, während es weitgehend offenläßt, wie  darauf reagiert wird.

Damit ist ein Ereignis insofern das Gegenstück  einer Operation : Der Empfänger (Implementierer) einer Operation  muß deren Spezifikation genau erfüllen, während der Absender diese nach Belieben aktiveren kann. Der Empfänger eines Ereignisses  kann dieses interpretieren wie er will, während der Absender das Ereignis genau nach dessen Spezifikation aussenden muß.

Ereignis-Schnittstellen

Eine Ereignis-Schnittstelle enthält verschiedene zusammengehörige Ereignisse.

Beispielsweise könnte eine einfache Tastatur die Taste "0", die Taste "1", die Taste "R", die Taste "E", die Taste "Q", die Taste "P", die Taste "J" und die Taste "K" besitzen.

Die Tastatur könnte entsprechende Ereignisse versenden, die Empfänger dann interpretieren können, wie sie wollen.

Damit Sender und Empfänger sich aufeinander abstimmen können, muß jedoch zunächst eine gemeinsame Schnittstelle aufgebaut werden.

KeyReceiver.java
/* Tastenschnittstelle */ 
interface KeyReceiver 
{ void key0(); void key1(); void keyR(); void keyE();  
void keyQ(); void keyP(); void keyJ(); void keyK(); }

Nun kann der Empfänger die Tasten implementieren, wie gewünscht. Hier nehmen wir an, der Empfänger solle einen Editor implementieren. Die Taste "0" und die Taste "1" bewirken das Anhängen des Zeichens "0" bzw. des Zeichens "1" an einen Textspeicher, die Taste "R" bewirkt das Löschen des letzten Zeichens des Textspeichers und die Taste "E" schließt die Bearbeitung ab. Die anderen Tasten sollen ignoriert werden.

In dem Quelltext "Editor.java" implementiert die Klasse "Editor" die Schnittstelle "KeyReceiver" und ist damit der Empfänger von Tastenereignissen. Sie verwendet wiederum die Klasse "EditAction" und die Klasse "EditBuffer" als „Hilfsklassen“, die aber keine Rolle im Rahmen der hier behandelten Ereignis-Schnittstelle spielen.

Editor.java
/* Tastenempfaenger */ 
class Editor implements KeyReceiver 
{ EditAction a; Editor(){ a = new EditAction(); } 
public void key0(){ a.insert0(); } 
public void key1(){ a.insert1(); } 
public void keyR(){ a.backspace(); } 
public void keyE(){ a.done(); } 
public void keyQ(){} public void keyP(){} 
public void keyJ(){} public void keyK(){}}
class EditAction 
{ EditBuffer b; EditAction(){ b = new EditBuffer(); } 
void insert0 (){ b.append( '0' ); } 
void insert1 (){ b.append( '1' ); } 
void backspace(){ b.removeLast(); } 
void done (){ b.print(); }}
class EditBuffer 
{ StringBuffer s; EditBuffer(){ s = new StringBuffer(); } 
void append( final char c ){ s.append( c ); } 
void removeLast(){ s.deleteCharAt( s.length() - 1 ); } 
void print(){ System.out.println( s ); }}

Die Klasse "Editor" schickt Nachrichten an die Klasse "EditAction", die Operationen aufrufen, die nun eine bestimmte Semantik haben und übersetzt damit die Nachrichten in bestimmte Aktionen. Die Klasse "EditAction" verwirklicht diese Operationen durch ein Objekt der Klasse "EditBuffer", welche die Bearbeitungsvorgänge auf dem Textspeicher wirklich umsetzt, wobei sie sich wiederum der Standardklasse "StringBuffer" bedient.

Die Art und Weise, wie hier die Klasse "EditBuffer" die Implementation der Klasse "StringBuffer" verwendet, ohne daß sie als Erweiterung dieser Klasse gekennzeichnet ist, wird auch als Delegation  bezeichnet. Die Klasse "EditBuffer" delegiert  bestimmte Aufgaben an die Klasse StringBuffer. Dazu verwendet sie ein Objekt dieser Klasse, das im Konstruktor beschafft wird. Entsprechendes gilt auch für die Klasse "EditAction" und die Klasse "Editor".

Da die Vererbung oft eine unnötig starke Bindung zwischen zwei Klassen festlegt, wird heute Delegation gegenüber der Vererbung in vielen Fällen bevorzugt.

So verwendet das ältere AWT  zur Weitergabe von Nachrichten auch noch mehr Vererbung, während das neuere Swing  dafür Delegation einsetzt. Manche (ältere) Lehrbücher betonen die Vererbung in Zusammenhang mit objektorientierter Programmierung auch zu sehr, so daß manchmal sogar der falsche Eindruck entsteht, daß objektorientierte Programmierung die Nutzung von Vererbung bedeuten muß.

Damit ein vollständiges Programm entsteht, wird nun noch eine Tastatur benötigt, welche die Tastenereignisse verschickt. Diese wird in der statischen Methode »sendKeysTo(KeyReceiver)« realisiert. Es handelt sich hierbei nicht um eine reale Tastatur, sondern nur um ein Software-Modell einer Tastatur, da in dieser Lektion die Anbindung an reale Tastaturen noch nicht behandelt werden soll. Auf dieser Modell-Tastatur werden dann die Taste "1", die Taste "Q", die Taste "0", die Taste "0", die Taste "R" und die Taste "E" angetippt.

Keyboard.java
public class Keyboard
{ private static void sendKeysTo( final KeyReceiver k ) 
{ k.key1(); k.keyQ(); k.key0(); k.key0(); k.keyR(); k.keyE(); }
public static void main( final java.lang.String[] args ) 
{ final KeyReceiver keyReceiver = new Editor(); 
sendKeysTo( keyReceiver ); }}

System.out
10

Ereignis-Adapter

Die Implementation eines Tastenempfängers kann noch etwas erleichtert werden. Der Tastenempfänger muß ja für jedes mögliche Ereignis eine Methode definieren, auch wenn das Ereignis gar keine Aktion bewirken soll. Daher mußten in der Deklaration der Klasse "Editor" einige Methoden mit leeren Rümpfen für vier Tasten definiert werden, die eigentlich nicht interessieren.

Falls eine bestimmte Schnittstelle mit mehreren Ereignissen voraussichtlich öfter implementiert wird, können alle Ereignisse einmal in einer abstrakten Basisklasse mit leeren Methodenrümpfen vordeklariert werden. Solch eine abstrakte Basisklasse wird auch als eine Adapterklasse  bezeichnet. Eine Empfängerklasse muß dann nur noch die Methoden überschreiben, deren Rumpf nicht leer sein sollen.

Editor1.java
abstract class KeyAdapter1 
{ public void key0(){} public void key1(){} 
public void keyR(){} public void keyE(){} 
public void keyQ(){} public void keyP(){} 
public void keyJ(){} public void keyK(){}}
/* Tastenempfaenger */ 
class Editor1 extends KeyAdapter1 implements KeyReceiver 
{ final EditAction a; 
Editor1(){ a = new EditAction(); } 
public void key0(){ a.insert0(); } 
public void key1(){ a.insert1(); } 
public void keyR(){ a.backspace(); } 
public void keyE(){ a.done(); }}

Diese Kombination aus einem Adapter und einem Empfänger ist zunächst aufwendiger als ein einzelner Empfänger, der Adapter lohnt sich aber dann, wenn später andere Empfänger geschrieben werden sollen, für die er wiederverwendet werden kann. Der Rumpf der Empfängerklasse ist jetzt kleiner geworden, weil sie nur noch die Implementation von Methoden mit nichtleeren Rümpfen enthält und alle anderen Implementation aus dem Adapter übernimmt.

Empfänger-Registrierung

In vielen Fällen soll ein Ereignis-Sender nicht fest auf einen Ereignis-Empfänger eingestellt sein, sondern seine Ereignisse an jeden  interessierten Ereignis-Empfänger senden. Dazu muß ein Ereignis-Empfänger zunächst beim Ereignis-Sender eingetragen (registriert) werden, daraufhin kann der Ereignis-Sender seine Ereignisse an den Ereignis-Empfänger schicken.

Registrierung und Ereignis-Lieferung
           1.) Registrierung 
.---------------------------------------. 
| 1 | 
V | 
.---------------------. .---------------------. 
| Ereignis-Sender | | Ereignis-Empfaenger | 
| | | | 
| | | | 
'---------------------' '---------------------' 
^ | ^ | 
| 1 | * | | 
| '---------------------------------------' | 
| 2.) Ereignisse | 
| | 
| 3.) Deregistrierung | 
'---------------------------------------------------------'

Wenn die Ereignisse nicht mehr benötigt werden, sollte sich der Ereignisempfänger wieder deregistrieren (die Registrierung rückgängig machen).

Das Programm "Keyboard1" enthält einen Ereignissender, der einen Empfänger akzeptieren kann und diesem dann Ereignisse schicken kann. In der Hauptmethode der Klasse "Keyboard1" wird zunächst ein Empfänger und ein Sender erzeugt. Der Empfänger "receiver" wird alsdann beim Sender "sender" eingetragen und anschließend wird die Steuerung mit der Operation »send()« an den Sender übergeben, der nun seine Ereignisse an den Empfänger schicken kann.

Die Methode "register" ergibt einen Wahrheitswert, der angibt, ob der Registrierungsversuch erfolgreich war. Dieser Wert ist hier immer der Wert "true", aber ein Registrierungsversuch kann grundsätzlich auch scheitern, etwa wenn kein Speicherplatz mehr zur Verfügung steht.

Keyboard1.java
class KeySender { KeyReceiver k; 
boolean register( final KeyReceiver k ){ this.k = k; return true; } 
void unregister( final KeyReceiver k ){ this.k = null; } 
void send() 
{ k.key1(); k.keyQ(); k.key0(); k.key0(); k.keyR(); k.keyE(); }}
public class Keyboard1 
{ public static void main( final String[] args ) 
{ final KeyReceiver receiver = new Editor(); 
final KeySender sender = new KeySender(); 
if( sender.register( receiver )) 
{ sender.send();  
sender.unregister( receiver ); }}}

System.out
10

Das Ereignisdelegationsmodell (delegation event model )

Eine Ereignisverarbeitung der hier beschriebenen Art, die darauf beruht, daß eingetragenen Empfängern Nachrichten geschickt werden, wird auch als Ereignisdelegationsmodell  (delegation event model ) bezeichnet.

Der Begriff „Ereignisdelegationsmodell“ ergibt sich daher, daß der Sender bei der Registrierung des Empfängers eine Referenz auf den Empfänger erhält und die Ereignisse dann an den Empfänger „delegiert“, indem er entsprechende Nachrichten an den Empfänger schickt. (Weiter oben delegierte die Klasse "Editor" die empfangenen Ereignisse dann wiederum an die Klasse "EditAction" weiter, was zufälligerweise auch eine Delegation ist, die aber nicht mehr zum Delegationsmodell gehört, sondern unabhängig davon erfolgt.) Das „Ereignisdelegationsmodell“ ist von einem „Erweiterungsmodell“ zu unterscheiden, bei dem eine Empfängerklasse eine Basisklasse erweitern  muß, um Ereignisse zu erhalten. Dieses Erweiterungsmodell wird hier aber nicht behandelt.

Im Ereignisdelegationsmodell wird die Behandlung von Ereignissen an bestimmte Empfänger delegiert, die eine passende Schnittstelle implementieren.

In der Anwendung des Ereignisdelegationsmodells werden oft auch noch Ereignisobjekte  verwendet, die einem Ereignisempfänger mit dem Ereignis zusammen als Argument übergeben werden, und weitere Informationen (Details) über das Ereignis enthalten.

Seiteninformationen und Impressum   |   Mitteilungsformular  |   "ram@zedat.fu-berlin.de" (ohne die Anführungszeichen) ist die Netzpostadresse von Stefan Ram.   |   Von der Stefan-Ram-Startseite ausgehend finden sich oft noch mehr Informationen zu Themen, die auf einer Seite angesprochen wurden. (Eine Verbindung zur Stefan-Ram-Startseite befindet sich ganz oben auf dieser Seite.)  |   Der Urheber dieses Textes ist Stefan Ram. Alle Rechte sind vorbehalten. Diese Seite ist eine Veröffentlichung von Stefan Ram. slrprd, PbclevtugFgrsnaEnz