Le design-pattern observer

Situation:

Le besoin consiste à notifier tout changement de l'état d'un objet aux objets dépendants (pour rester simple, la notification est unidirectionnelle).
Le premier objet est appelé "observable" ou observé et les objets qui dépendent se nomment "observer" ou observateurs.
Les observateurs peuvent répercuter le changement notifié de façon dynamique.
La dépendance entre objet observable et objets observateurs est également gérée dynamiquement : On peut ajouter/retirer un observateur à tout moment sans bien sûr affecter le code des classes existantes.

Solution(avec API JAVA):
La classe de l'objet "observé" doit étendre la classe "Observable" et ainsi l'objet ajoute un observateur via la méthode addObserver,
Les objets observateurs doivent implémenter l'interface Observer et par conséquent implémenter la méthode "void update(Observable obs, Object obj)",
Au moment du changement dans l'état de l'objet observé, notifier les observateurs (par exemple, sur le setter de cet attribut).
NB. Tous les détails sont données dans la partie mise en pratique ci-après.

Avantages:

  • Ne pas réinventer la roue,
  • La solution offerte par les API Java répond à la situation posée et est robuste,
  • Facile à mettre en place.



Mise en pratique:

Une entreprise (siège) peut avoir ou être liée à plusieurs établissements. On souhaite notifier tout changement survenu dans "Entreprise" aux objets liés, de type "Etablissement" et ajoutés comme observateurs.
Le code des classes java ci-après montre comment notifier dynamiquement tout changement dans l'objet observé aux observateurs inscrits.
On commence par écrire une classe "Entreprise" de l'objet observé. Celle-ci étend la classe Observable et doit notifier les observateurs via les deux méthodes "setChanged" et "notifyObservers" de la classe mère Observable comme le montre le code java qui suit.
Ensuite, on donne le code la classe "Etablissement" qui implémente l'interface "Observer".

//la classe Entreprise 
public class Entreprise extends Observable {
	 private int id;
	 private boolean etat;
	 public void setEtat(boolean etat){
		this.etat=etat;		
		setChanged();
		notifyObservers(this.etat);		
	 } 
	 public boolean isEtat(){ return etat;}
	//autre setters/getters ...
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String toString(){
		return "Entreprise (id="+id+" etat="+etat+")";
	}
}


De l'autre côté, la classe "observateur" doit implémenter l'interface "Observer" ce qui exige de définir la méthode "void update(Observable obs, Object obj)".
C'est dans cette méthode qu'il faut indiquer le code métier qui sera exécuté à chaque modification de l'attribut surveillé de l'objet "Entreprise".
Bien évidemment, l'objet observé doit ajouter/déclarer un ou plusieurs observateur(s) pour que le processus de notification soit activé.
Dans l'exemple, à chaque fois que l'attribut "etat" change, une notification est envoyée aux observateurs "Etablissement".
A la réception de la notification, chaque observateur "Etablissemet" actualise l'attribut "infos" avec la chaîne "Notification reçu le...".
Notez que la réécriture de la méthode toString() de la classe Etablissement permet d'afficher sur la console cet objet avant et après toute modification de l'état de l'objet "Entreprise".

public class Etablissement implements Observer{
 
	private int id;
	private String infos;
	public void update(Observable obs, Object obj) {
		if(obs instanceof Entreprise){
			Calendar calendar=new GregorianCalendar(Locale.FRANCE);
			Date date=calendar.getTime();
			setInfos("Notification recu le "+new SimpleDateFormat().format(date));
		}
	}
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getInfos() {
		return infos;
	}
	public void setInfos(String infos) {
		this.infos = infos;
	}
	public String toString(){
		return "Etablissement ( id="+id+" infos :"+infos+")";
	}
}


Et on termine avec le client (Main.java) qui illustre les différentes notions expliquées:

public class Main{
 
		//définir un etablissement
		Etablissement etab=new Etablissement();
		etab.setId(1);
		etab.setInfos("RAS");
 
		//definir une entreprise
		Entreprise ent=new Entreprise();
		//Ajouter un observer	
		ent.addObserver(etab);
		System.out.print("Avant mise à jour de l'etat de l'entreprise:
\tEtab: "+ etab);
		//Effectuer un changement sur l'attribut etat de l'entreprise
		ent.setEtat(true);	
		System.out.print("Après mise à jour de l'etat de l'entreprise:
\tEtab: "+ etab);
}


L'exécution de ce code affiche sur la console:

Avant mise à jour de l'etat de l'entreprise:
	Etab: Etablissement ( id=1 infos :RAS)
Après mise à jour de l'etat de l'entreprise:
	Etab: Etablissement ( id=1 infos :Notification recu le 18/08/09 19:51)


Résumons les étapes nécessaires:

  1. - La classe de l'objet observé doit étendre la classe "Observable". Elle doit également appeler les méthodes "setChanged" et "notifyObservers" sur l'attribut à surveiller.
  2. - La classe de l'observateur doit implémenter l'interface "Observer" et par conséquent définir la méthode "void update(Observable obs, Object obj)" avec le code métier approprié.
  3. - Enfin, dans le client ajouter à l'observable son/ses observateurs via la méthode "addObserver".



Conclusion

Vous avez entre les mains un design qui va vous faciliter certaines tâches de développement.
L'usage de l'API robuste de Java vous épargne les difficultés de coder par vous-même les notifications entre objets et la gestion de multithread liée.
Vous pourriez dire qu'avec ce design les changements entre entités liées sont maitrisées avec un coût réduit.
Dans un prochain billet, je vous présente un nouveau design qui nous (vous) permettra de supporter au mieux (ne le dites pas fort!) les demandes incessantes du chef de projet (et du client) de faire changer et rechanger le code des règles métier durant le développement du projet.


Donc à la prochaine fois... pour parler de stratégie!