I. Introduction▲
L'écriture d'IHM java mène généralement à l'écriture de plusieurs listeners, souvent sous la forme de classes anonymes. Celles-ci sont fastidieuses à écrire et réduisent la lisibilité du code pour au final ne faire qu'une simple affectation ou un simple appel de méthode.
La classe java.beans.EventHandler permet de s'affranchir de cette écriture en permettant de générer dynamiquement des instances d'interface dont les méthodes exécutent une instruction simple. Son mécanisme est basé sur la création de proxy (cf java.lang.reflect.Proxy, java.lang.reflect.InvocationHandler).
Son utilisation est plus efficace que les classes anonymes,
notamment pour les grandes applications dans lesquelles une même
interface est implémentée de nombreuses fois. Elle permet de
réduire la taille et l'empreinte mémoire de l'application.
La raison de cette faible empreinte est que la classe proxy utilisée
par l'EventHandler partage les implémentations pour des interfaces
identiques. Par exemple, pour créer tous les ActionListeners d'une
application, une seule implémentation sera créée par le proxy. Globalement,
une implémentation est créée par interface alors qu'en utilisant des classes
anonymes, une implémentation est créée pour chaque listener.
Par exemple pour une application comptant 11 ActionListeners et
3 MouseListeners, 2 implémentations seront créées avec l'EventHandler
(une pour les ActionListeners et une pour les MouseListeners) alors
que l'utilisation de classes anonymes en aurait créé 14 (une pour
chaque listener).
Son inconvénient est que l'utilisation de la réflection rend l'exécution plus coûteuse. Ce coût est négligeable pour des exécutions ponctuelles (action d'un clic bouton par exemple) mais cela peut nuire aux performances dans le cas d'appels successifs (appels dans une boucle).
La classe EventHandler permet globalement de gérer plusieurs cas d'utilisation simples via ses méthodes statiques create(...).
II. Appel simple d'une méthode▲
Le premier cas d'utilisation est celui d'un simple appel à une méthode donnée (l'action) sur un objet donné (la cible). Il est géré par la méthode statique EventHandler.create(Class, Object, String).
Prenons un exemple simple :
button.addActionListener
(
new
ActionListener
(
) {
@Override
public
void
actionPerformed
(
ActionEvent e) {
foo
(
);
}
}
);
Ce code crée une instance de l'interface ActionListener afin
d'effectuer un appel à la méthode foo() de l'objet englobant la
classe anonyme.
Dans notre cas d'utilisation, la méthode foo() est l'action et
l'objet englobant est la cible.
Avec la classe EventHandler, le code devient :
button.addActionListener
(
EventHandler.create
(
ActionListener.class
, this
, "foo"
));
Le premier paramètre est le type de l'instance qui doit être créée.
Le deuxième paramètre est la cible sur laquelle sera effectuée l'action (ici this).
Le troisième paramètre est l'action à effectuer sur la cible (la méthode à
exécuter, ici foo()).
Un autre exemple :
button.addActionListener
(
new
ActionListener
(
) {
@Override
public
void
actionPerformed
(
ActionEvent e) {
helloworld.sayHello
(
);
}
}
);
Devient :
button.addActionListener
(
EventHandler.create
(
ActionListener.class
, helloworld, "sayHello"
));
III. Appel d'une méthode avec une propriété de l'évènement comme argument▲
L'EventHandler permet également de pouvoir spécifier à l'action une des propriétés de l'évènement avec la méthode statique EventHandler.create(Class, Object, String, String).
Soit le code :
new
ActionListener
(
) {
@Override
public
void
actionPerformed
(
ActionEvent e) {
doAction
(
e.getSource
(
));
}
}
;
...
public
void
doAction
(
Object object) {
System.out.println
(
"action sur "
+
object);
}
Qui, avec l'EventHandler devient :
EventHandler.create
(
ActionListener.class
, this
, "doAction"
, "source"
);
...
public
void
doAction
(
Object object) {
System.out.println
(
"action sur "
+
object);
}
L'utilisation des propriétés nécessite la présence des getters appropriés, la propriété "source" implique la présence de la méthode getSource().
Il est également possible d'indiquer une sous-propriété en utilisant le caractère ".". Ainsi le code suivant utilisant la propriété text de la propriété source de l'évènement :
new
ActionListener
(
) {
@Override
public
void
actionPerformed
(
ActionEvent e) {
setTitle
(((
JTextField) e.getSource
(
)).getText
(
) );
}
}
;
Devient avec l'EventHandler :
EventHandler.create
(
ActionListener.class
, this
, "setTitle"
, "source.text"
);
Il est possible d'enchaîner les sous-propriétés. Par exemple :
EventHandler.create
(
ActionListener.class
, this
, "setAdminMode"
, "source.user.admin"
);
est équivalent à :
new
ActionListener
(
) {
@Override
public
void
actionPerformed
(
ActionEvent e) {
setAdminMode
(((
UserAuth) e.getSource
(
)).getUser
(
).isAdmin
(
));
}
}
;
Il est aussi possible de passer l'évènement lui-même en spécifiant une chaîne vide "" comme propriété :
EventHandler.create
(
ActionListener.class
, this
, "doAction"
, ""
);
&
#8230
;
public
void
doAction
(
ActionEvent e) {
System.out.println
(
"action sur "
+
e.getSource
(
));
}
IV. Choix des méthodes de l'interface▲
Par défaut, le code expliqué précédemment s'applique à toutes les méthodes de l'interface. Par exemple le code :
EventHandler.create
(
FocusListener.class
, this
, "focusChanged"
, ""
);
est équivalent à :
new
FocusListener
(
) {
@Override
public
void
focusGained
(
FocusEvent e) {
focusChanged
(
e);
}
@Override
public
void
focusLost
(
FocusEvent e) {
focusChanged
(
e);
}
}
;
Pour n'appliquer une action qu'à une méthode précise de l'interface, il est possible de spécifier son nom avec la troisième méthode statique EventHandler.create(Class, Object, String, String, String). Les autres méthodes ne feront alors rien.
EventHandler.create
(
FocusListener.class
, this
, "focusChanged"
, ""
, "focusGained"
);
est équivalent à :
new
FocusListener
(
) {
@Override
public
void
focusGained
(
FocusEvent e) {
focusChanged
(
e);
}
@Override
public
void
focusLost
(
FocusEvent e) {
}
}
;
V. Cas de la méthode cible surchargée▲
Il est recommandé de ne pas appeler une méthode surchargée comme action. En effet, si par exemple la cible est une instance de la classe suivante :
public
class
Target {
public
void
doAction
(
String);
public
void
doAction
(
Object);
}
L'EventHandler appellera la méthode appropriée en fonction de la source. Cependant, si la source vaut null, les deux méthodes sont appropriées et il n'y a aucune garantie de laquelle sera appelée. Pour cette raison il est déconseillé d'appeler une méthode surchargée comme action.
VI. Conclusion▲
Même si l'EventHandler ne couvre que des cas d'utilisation relativement basiques, ceux-ci représentent une grande majorité des cas rencontrés dans une application. De plus, même si son utilisation est particulièrement adaptée aux listeners, elle peut s'utiliser pour créer une instance de n'importe quelle interface.
EventHandler.create
(
Runnable.class
, "execute"
);
Ainsi son principal intérêt est donc de grandement réduire le nombre de classes anonymes ce qui apporte une plus grande lisibilité du code en plus d'une plus faible empreinte mémoire.
VII. Remerciements▲
Je remercie Gueritarish et Erielle pour leurs remarques et corrections.