Vers une gestion personnalisée des événements avec Java ∞
programmation
Dans le cadre du développement d'applications, il est de plus en plus indispensable de structurer le code selon le patron : Modèle, Vue et Contrôleur ou MVC. Cette séparation des différentes logiques nécessite la mise en place d'une gestion d'événements personnalisé afin que lorsque le modèle change, la vue en soit informé et vis et versa, quand l'utilisateur interagit sur la vue, le modèle doit prendre en compte les modifications.
Si des tas d'usine à gaz existent et vous proposent des patrons MVC tout prêt vous aurez peut-être besoin de développer vous même votre propre moteur.
Les éléments doivent être indépendant les uns des autres et la communication s'effectue avec l'aide d'événements. Pour y arriver un mécanisme d'événements et d'écouteurs doit être mis en place selon le patron de conception (Design Pattern ou DP) "Observateur". Avant d'aller plus loin, il est important de comprendre le fonctionnement de ce patron. Vous trouverez toute la documentation nécessaire à son implémentation sur wikipedia.
Conception
L'idée est donc ici d'implémenter des classes qui s'inspirent du DP Observer. En me basant sur mon expérience avec AS3, j'ai tenté une approche avec la modélisation suivante :
Pour illustrer ma gestion d'événements, j'ai choisi d'implémenter un pseudo modèle MVC : ViewClass
et
ModelClass
étant des classes d'événements et ControllerClass
étant l'écouteur. Pour produire un événement,
les classes d'événements doivent préciser, au travers de la méthode dispatchEvent
, le type l'événement qui s'est produit.
Pour écouter, ControllerClass
doit appeler la méthode addEventListener
sur chacun des objets, être lui-même l'objet de retour
et implémenter la méthode eventPerformed
qui sera appelé à chaque événements (grâce à MyEventListener).
Implémentation
Plutôt que des mots, démo, passons au code avec tout d'abord l'ensemble des classes de gestion des écouteurs/événements :
MyEventListener.java
MyEventEnum.java
MyEvent.java
<span class="c1">// forcer la création d'événements avec une source et un type</span>
<span class="kd">public</span> <span class="nf">MyEvent</span><span class="o">(</span><span class="n">Object</span> <span class="n">eventSource</span><span class="o">,</span> <span class="n">MyEventEnum</span> <span class="n">eventType</span><span class="o">)</span> <span class="o">{</span>
<span class="k">this</span><span class="o">.</span><span class="na">eventSource</span> <span class="o">=</span> <span class="n">eventSource</span><span class="o">;</span>
<span class="k">this</span><span class="o">.</span><span class="na">eventType</span> <span class="o">=</span> <span class="n">eventType</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="n">Object</span> <span class="nf">getEventSource</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">eventSource</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="n">MyEventEnum</span> <span class="nf">getEventType</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">eventType</span><span class="o">;</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="n">String</span> <span class="nf">toString</span><span class="o">()</span> <span class="o">{</span>
<span class="k">return</span> <span class="n">eventSource</span><span class="o">.</span><span class="na">getClass</span><span class="o">().</span><span class="na">toString</span><span class="o">();</span>
<span class="o">}</span>
}
MyEventDispatcher.java
public abstract class MyEventDispatcher { // Tableau associatif : MyEventEnum => Tableau de MyEventListener private Hashtable<MyEventEnum, ArrayList<MyEventListener>> listeners = new Hashtable<MyEventEnum, ArrayList<MyEventListener>>();
<span class="kd">public</span> <span class="kd">synchronized</span> <span class="kt">void</span> <span class="nf">addEventListener</span><span class="o">(</span><span class="n">MyEventEnum</span> <span class="n">eventType</span><span class="o">,</span> <span class="n">MyEventListener</span> <span class="n">list</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// si l'eventType est déjà présent dans le tableau associatif</span>
<span class="k">if</span> <span class="o">(</span><span class="n">listeners</span><span class="o">.</span><span class="na">containsKey</span><span class="o">(</span><span class="n">eventType</span><span class="o">))</span> <span class="o">{</span>
<span class="n">listeners</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">eventType</span><span class="o">).</span><span class="na">add</span><span class="o">(</span><span class="n">list</span><span class="o">);</span>
<span class="c1">// sinon :</span>
<span class="o">}</span> <span class="k">else</span> <span class="o">{</span>
<span class="c1">// création</span>
<span class="n">ArrayList</span><span class="o"><</span><span class="n">MyEventListener</span><span class="o">></span> <span class="n">t</span> <span class="o">=</span> <span class="k">new</span> <span class="n">ArrayList</span><span class="o"><</span><span class="n">MyEventListener</span><span class="o">>();</span>
<span class="n">listeners</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">eventType</span><span class="o">,</span> <span class="n">t</span><span class="o">);</span>
<span class="c1">// puis appel récursif</span>
<span class="n">addEventListener</span><span class="o">(</span><span class="n">eventType</span><span class="o">,</span> <span class="n">list</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">public</span> <span class="kd">synchronized</span> <span class="kt">void</span> <span class="nf">removeEventListener</span><span class="o">(</span><span class="n">MyEventEnum</span> <span class="n">eventType</span><span class="o">,</span> <span class="n">MyEventListener</span> <span class="n">list</span><span class="o">)</span> <span class="o">{</span>
<span class="k">if</span> <span class="o">(</span><span class="n">listeners</span><span class="o">.</span><span class="na">containsKey</span><span class="o">(</span><span class="n">eventType</span><span class="o">))</span> <span class="o">{</span>
<span class="n">listeners</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">eventType</span><span class="o">).</span><span class="na">remove</span><span class="o">(</span><span class="n">list</span><span class="o">);</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="kd">protected</span> <span class="kt">boolean</span> <span class="nf">dispatchEvent</span><span class="o">(</span><span class="n">MyEvent</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// si le type est bien présent dans la liste</span>
<span class="k">if</span> <span class="o">(</span><span class="n">listeners</span><span class="o">.</span><span class="na">containsKey</span><span class="o">(</span><span class="n">e</span><span class="o">.</span><span class="na">getEventType</span><span class="o">()))</span> <span class="o">{</span>
<span class="c1">// on récupère tous les écouteurs</span>
<span class="n">Iterator</span><span class="o"><</span><span class="n">MyEventListener</span><span class="o">></span> <span class="n">l</span> <span class="o">=</span> <span class="n">listeners</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">e</span><span class="o">.</span><span class="na">getEventType</span><span class="o">()).</span><span class="na">iterator</span><span class="o">();</span>
<span class="k">while</span><span class="o">(</span><span class="n">l</span><span class="o">.</span><span class="na">hasNext</span><span class="o">())</span> <span class="o">{</span>
<span class="n">MyEventListener</span> <span class="n">gl</span> <span class="o">=</span> <span class="n">l</span><span class="o">.</span><span class="na">next</span><span class="o">();</span>
<span class="c1">// pour chaque écouteur, on invoque la méthode eventPerformed</span>
<span class="n">gl</span><span class="o">.</span><span class="na">eventPerformed</span><span class="o">(</span><span class="n">e</span><span class="o">);</span>
<span class="o">}</span>
<span class="k">return</span> <span class="kc">true</span><span class="o">;</span>
<span class="o">}</span>
<span class="k">return</span> <span class="kc">false</span><span class="o">;</span>
<span class="o">}</span>
}
Maintenant, voici le code d'implémentation MVC utilisant le système :
MyEventEnumModel.java
MyEventEnumView.java
ModelClass.java
<span class="c1">// exemple d'utilisation...</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">foo</span><span class="o">()</span> <span class="o">{</span>
<span class="c1">// ToDo....</span>
<span class="n">event1</span><span class="o">();</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kt">void</span> <span class="nf">event1</span><span class="o">()</span> <span class="o">{</span>
<span class="n">dispatchEvent</span><span class="o">(</span><span class="k">new</span> <span class="nf">MyEvent</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">MyEventEnumModel</span><span class="o">.</span><span class="na">MODEL_EVENT1</span><span class="o">));</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kt">void</span> <span class="nf">event2</span><span class="o">()</span> <span class="o">{</span>
<span class="n">dispatchEvent</span><span class="o">(</span><span class="k">new</span> <span class="nf">MyEvent</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">MyEventEnumModel</span><span class="o">.</span><span class="na">MODEL_EVENT2</span><span class="o">));</span>
<span class="o">}</span>
}
ViewClass.java
<span class="c1">// exemple d'utilisation...</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">foo</span><span class="o">()</span> <span class="o">{</span>
<span class="c1">// ToDo....</span>
<span class="n">event1</span><span class="o">();</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kt">void</span> <span class="nf">event1</span><span class="o">()</span> <span class="o">{</span>
<span class="n">dispatchEvent</span><span class="o">(</span><span class="k">new</span> <span class="nf">MyEvent</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">MyEventEnumView</span><span class="o">.</span><span class="na">VIEW_EVENT1</span><span class="o">));</span>
<span class="o">}</span>
<span class="kd">private</span> <span class="kt">void</span> <span class="nf">event2</span><span class="o">()</span> <span class="o">{</span>
<span class="n">dispatchEvent</span><span class="o">(</span><span class="k">new</span> <span class="nf">MyEvent</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">MyEventEnumView</span><span class="o">.</span><span class="na">VIEW_EVENT2</span><span class="o">));</span>
<span class="o">}</span>
}
Pour finir par le contrôleur :
ControllerClass.java
<span class="kd">public</span> <span class="nf">ControllerClass</span><span class="o">()</span> <span class="o">{</span>
<span class="n">model</span> <span class="o">=</span> <span class="k">new</span> <span class="nf">ModelClass</span><span class="o">();</span>
<span class="n">view</span> <span class="o">=</span> <span class="k">new</span> <span class="nf">ViewClass</span><span class="o">();</span>
<span class="n">model</span><span class="o">.</span><span class="na">addEventListener</span><span class="o">(</span><span class="n">MyEventNumModel</span><span class="o">.</span><span class="na">MODEL_EVENT1</span><span class="o">,</span> <span class="k">this</span><span class="o">);</span>
<span class="n">model</span><span class="o">.</span><span class="na">addEventListener</span><span class="o">(</span><span class="n">MyEventNumModel</span><span class="o">.</span><span class="na">MODEL_EVENT2</span><span class="o">,</span> <span class="k">this</span><span class="o">);</span>
<span class="n">view</span><span class="o">.</span><span class="na">addEventListener</span><span class="o">(</span><span class="n">MyEventNumView</span><span class="o">.</span><span class="na">VIEW_EVENT1</span><span class="o">,</span> <span class="k">this</span><span class="o">);</span>
<span class="n">view</span><span class="o">.</span><span class="na">addEventListener</span><span class="o">(</span><span class="n">MyEventNumView</span><span class="o">.</span><span class="na">VIEW_EVENT2</span><span class="o">,</span> <span class="k">this</span><span class="o">);</span>
<span class="o">}</span>
<span class="c1">// gestion des événements du modèle de la vue</span>
<span class="kd">public</span> <span class="kt">void</span> <span class="nf">eventPerformed</span><span class="o">(</span><span class="n">GameEvent</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// si l'événement est levé par le modèle</span>
<span class="k">if</span> <span class="o">(</span><span class="n">e</span><span class="o">.</span><span class="na">getEventSource</span><span class="o">()</span> <span class="k">instanceof</span> <span class="n">ModelClass</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// quel est l'événement ?</span>
<span class="k">switch</span><span class="o">((</span><span class="n">MyEventNumModel</span><span class="o">)</span><span class="n">e</span><span class="o">.</span><span class="na">getEventType</span><span class="o">())</span> <span class="o">{</span>
<span class="k">case</span> <span class="nl">MODEL_EVENT1:</span>
<span class="k">break</span><span class="o">;</span>
<span class="k">case</span> <span class="nl">MODEL_EVENT2:</span>
<span class="k">break</span><span class="o">;</span>
<span class="o">}</span>
<span class="c1">// sinon si l'événement est levé par la vue</span>
<span class="o">}</span> <span class="k">else</span> <span class="nf">if</span> <span class="o">(</span><span class="n">e</span><span class="o">.</span><span class="na">getEventSource</span><span class="o">()</span> <span class="k">instanceof</span> <span class="n">ViewClass</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// quel est l'événement ?</span>
<span class="k">switch</span><span class="o">((</span><span class="n">MyEventNumView</span><span class="o">)</span><span class="n">e</span><span class="o">.</span><span class="na">getEventType</span><span class="o">())</span> <span class="o">{</span>
<span class="k">case</span> <span class="nl">VIEW_EVENT1:</span>
<span class="k">break</span><span class="o">;</span>
<span class="k">case</span> <span class="nl">VIEW_EVENT2:</span>
<span class="k">break</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
}
Cette implémentation fonctionne très bien.
Toutefois on pourrait lui reprocher le fait que les objets qui émettent des événements,
à savoir ViewClass
et ModelClass
, soient si fortement liés à la classe MyEventDispatcher
que cela bloque tout autre héritage. Des solutions existent, mais me concernant cela fera
l'objet d'un prochain article.
Références
http://rom.developpez.com/java-listeners/