BlackSmurf

Vers une gestion personnalisée des événements avec Java


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.

Modèle Vue Contrôleur
fig. 1 – Principe du MVC

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.

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 :


fig 2. – Diagramme de classes : Evénements personnalisés

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).

Plutôt que des mots, démo, passons au code avec tout d’abord l’ensemble des classes de gestion des écouteurs/événements :

public interface MyEventListener {
	public void eventPerformed(MyEvent e);
}
public interface MyEventEnum {}
public class MyEvent {
	private transient Object eventSource;
	private MyEventNum eventType;

	// forcer la création d'événements avec une source et un type
	public MyEvent(Object eventSource, MyEventNum eventType) {
		this.eventSource = eventSource;
		this.eventType = eventType;
	}

	public Object getEventSource() {
		return eventSource;
	}

	public MyEventNum getEventType() {
		return eventType;
	}

	public String toString() {
		return eventSource.getClass().toString();
	}
}
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.Iterator;

public abstract class MyEventDispatcher {
	// Tableau associatif : MyEventEnum => Tableau de MyEventListener
	private Hashtable<MyEventEnum, ArrayList<MyEventListener>> listeners = new Hashtable<MyEventEnum, ArrayList<MyEventListener>>();

	public synchronized void addEventListener(MyEventEnum eventType, MyEventListener list) {
		// si l'eventType est déjà présent dans le tableau associatif
		if (listeners.containsKey(eventType)) {
			listeners.get(eventType).add(list);

		// sinon :
		} else {
			// création
			ArrayList<MyEventListener> t = new ArrayList<MyEventListener>();
			listeners.put(eventType, t);
			// puis appel récursif
			addEventListener(eventType, list);
		}

	}

	public synchronized void removeEventListener(MyEventEnum eventType, MyEventListener list) {
		if (listeners.containsKey(eventType)) {
			listeners.get(eventType).remove(list);
		}
	}

	protected boolean dispatchEvent(MyEvent e) {

		// si le type est bien présent dans la liste
		if (listeners.containsKey(e.getEventType())) {

			// on récupère tous les écouteurs
			Iterator<MyEventListener> l = listeners.get(e.getEventType()).iterator();
			while(l.hasNext()) {
				MyEventListener gl = l.next();

				// pour chaque écouteur, on invoque la méthode eventPerformed
				gl.eventPerformed(e);
			}

			return true;
		}

		return false;
	}
}

Maintenant, voici le code d’implémentation MVC utilisant le système :

public enum MyEventEnumModel implements MyEventEnum {
	MODEL_EVENT1, MODEL_EVENT2;
}
public enum MyEventEnumView implements MyEventEnum {
	VIEW_EVENT1, VIEW_EVENT2;
}
public class ModelClass extends MyEventDispatcher {

	// exemple d'utilisation...
	public void foo() {
		// ToDo....
		event1();
	}

	private void event1() {
		dispatchEvent(new MyEvent(this, MyEventEnumModel.MODEL_EVENT1));
	}

	private void event2() {
		dispatchEvent(new MyEvent(this, MyEventEnumModel.MODEL_EVENT2));
	}
}
public class ViewClass extends MyEventDispatcher {

	// exemple d'utilisation...
	public void foo() {
		// ToDo....
		event1();
	}

	private void event1() {
		dispatchEvent(new MyEvent(this, MyEventEnumView.VIEW_EVENT1));
	}

	private void event2() {
		dispatchEvent(new MyEvent(this, MyEventEnumView.VIEW_EVENT2));
	}
}

Pour finir par le contrôleur :

// contrôleur
public class ControllerClass implements MyEventListener {
	private ModelClass model;
	private ViewClass view;

	public ControllerClass() {
		model = new ModelClass();
		view = new ViewClass();

		model.addEventListener(MyEventNumModel.MODEL_EVENT1, this);
		model.addEventListener(MyEventNumModel.MODEL_EVENT2, this);

		view.addEventListener(MyEventNumView.VIEW_EVENT1, this);
		view.addEventListener(MyEventNumView.VIEW_EVENT2, this);
	}

	// gestion des événements du modèle de la vue
	public void eventPerformed(GameEvent e) {

		// si l'événement est levé par le modèle
		if (e.getEventSource() instanceof ModelClass) {

			// quel est l'événement ?
			switch((MyEventNumModel)e.getEventType()) {

				case MODEL_EVENT1:
					break;

				case MODEL_EVENT2:
					break;
			}

		// sinon si l'événement est levé par la vue
		} else if (e.getEventSource() instanceof ViewClass) {

			// quel est l'événement ?
			switch((MyEventNumView)e.getEventType()) {

				case VIEW_EVENT1:
					break;

				case VIEW_EVENT2:
					break;
			}
		}

	}
}

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/

Laisser un commentaire