Si vous n’êtes pas encore familier avec les design pattern (patrons de conception en français), il vous est certainement arrivé lors de l’écriture de programmes un tant soit peu complexe de vous sentir un peu… perdu. Face à une multiplicité de classes toutes plus ou moins dépendantes les unes des autres, on a vite fait de se retrouver devant un code spaghetti impossible à maintenir. Ajoutez par dessus l’apparition d’un bug (ce qui arrivera presque toujours) et vous êtes bon pour une sérieuse migraine.
Heureusement, les design pattern sont là. Pour faire simple, ce concept désigne un ensemble de méthodes éprouvées pour concevoir efficacement un programme. En gros les classes sont vues comme des legos, et les patrons de conception vous indiquent comment les imbriquer entre eux.

Un exemple parmi d’autres: l’adaptateur
Un de vos programmes utilise une classe C1 implémentant l’interface I1. Un jour vous découvrez l’existence d’une classe C2 proposant les même fonctionnalités que C1, mais codée de manière plus efficace. Or C2 implémente l’interface I2, radicalement différente de I1.
Question: comment utiliser C2, sans devoir chambouler toute votre application?
Réponse: en codant un adapteur, un patron de conception de type structure. Dans cet exemple, concevoir un adapteur revient à écrire une interface I3 qui héritera de I1 et I2 et dont l’implémentation se chargera de dérouter tous les appels aux anciennes fonctions (celles de C1) vers les nouvelles (celles de C2). Ainsi, les appels de fonction à l’intérieur de votre application restent inchangés, mais en arrière plan se sont en réalités les fonctions de C2 qui sont utilisées.
L’idée est simple, il suffisait juste d’y penser, et les design pattern regorgent d’idées simples auxquelles on devrait toujours penser.

Un autre modèle de conception: l’observateur.
Utilisable typiquement quand vous codez une IHM, il ne s’agit ni plus ni moins que d’avertir proprement un objet lorsque l’état d’un autre objet a été modifié.
La mise en oeuvre de ce patron m’a demandé un peu plus de temps que celle de l’adaptateur parce qu’il fait appel à des éléments de l’API java que je n’avais pas l’habitude d’utiliser. Et si j’en juge par ce que j’ai trouvé sur le net après une rapide recherche, je dois pas être le seul dans ce cas, à vous d’en juger à partir des deux exemples de conception qui suivent. Dans les deux cas on possède deux classes, Observateur et Cible, et on souhaite notifier l’objet Observateur lorsque la Cible qui lui est passée en paramètre est modifiée.

Première solution.
Cette solution est proposée par le site vogella. je l’ai appliqué de manière à tenir compte de notre situation Observateur/Cible.

La cible pour commencer:

import java.beans.PropertyChangeListener;
 
public class Cible {
 
	private PropertyChangeListener listener;
 
	private int valeur;
 
	public Cible(int valeur) {
		this.valeur = valeur;
	}
 
	public int getValeur() {
		return valeur;
	}
 
	public void setValeur(int valeur) {
		this.valeur = valeur;
		listener.propertyChange(null);
	}
 
	public void addChangeListener(PropertyChangeListener newListener) {
		this.listener = newListener;
	}
}

La cible contient une valeur (l’élément que l’on souhaite en réalité surveiller) ainsi qu’un objet de type PropertyChangeListener (qui est en réalité une référence vers notre Observateur comme nous le verrons plus bas). On voit que la méthode addChangeListener permet justement à la cible d’obtenir la référence de notre observateur, et la méthode setValeur dont on voit à la fin qu’elle fait un appel à propertyChange de notre observateur. On imagine que c’est dans la méthode propertyChange que nous écriront le code qui traitera le changement d’état.

Bien c’est au tour de la classe Observateur à présent:

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
 
public class Observateur implements PropertyChangeListener {
	public Observateur(Cible cible) {
		cible.addChangeListener(this);
	}
 
	public void propertyChange(PropertyChangeEvent evt) {
		System.out.println("la cible a changé");
	}
}

Comme prévu la cible lui est passée en paramètre, et l’implémentation de l’interface PropertyChangeListener nécessite effectivement la présence de la méthode propertyChange. J’ai fait au plus simple pour le traitement du changement d’état: un Sopln fera l’affaire pour cet exemple.

Ajoutons à celà un main de base:

public class Main {
	public static void main(String[] args) {
		Cible cible = new Cible(5);
		Observateur obs = new Observateur(cible);
		cible.setValeur(10);
	}
}

Et voilà. Compilation, exécution, ça fonctionne.
Une rapide analyse du déroulement du programme: l’observateur possède une référence vers sa cible (elle lui est passée en paramètre du constructeur) et la cible une référence vers son observateur (grâce à addChangeListener). Dans le code de setValeur, une fois que la variable a été surveillée, on notifie l’observateur en appelant sa méthode propertyChange.
Ça fonctionne mais j’ai un peu tiqué en voyant le code, cette histoire de PropertyChangeListener qu’il faut déclarer dans la cible, et ce null placé en paramètre de propertyChange… on est pas censé avoir à s’occuper de ça ici… Il doit bien y avoir un moyen de déléguer ce travail à une autre classe… Et effectivement, il y a moyen. Et c’est tant mieux. Et c’est wikipedia qui nous le propose (comme quoi on peut y lire des articles tout à fait correct ["mais pas toujours!" me souffle mon prof de système d'exploitation...]).

Deuxième solution.
Voici donc une deuxième solution, plus claire et plus efficace:

import java.util.Observable;
 
public class Cible extends Observable {
	private int valeur;
 
	public Cible(int valeur) {
		this.valeur = valeur;
	}
 
	public int getValeur() {
		return valeur;
	}
 
	public void setValeur(int valeur) {
		this.valeur = valeur;
		this.setChanged();
		this.notifyObservers();
	}
}

Première nouveauté: la cible étend la classe Observable. C’est sans doute à l’intérieur de celle ci que la gestion de la communication avec l’écouteur a été placée. D’ailleurs autant s’en assurer, un petit tour dans le code de Observable, et voici ce qu’on y trouve:

public synchronized void addObserver(Observer o) {
	if (o == null)
		throw new NullPointerException();
	if (!obs.contains(o))
		obs.addElement(o);
}

Et en plus la classe gère les accès concurrents, parfait.
Mais revenons à notre classe Cible: dans setValeur on retrouve un appel à setChanged (qui se contente de mettre une variable de Observable à true) et un à notifyObserver, dont le rôle est d’avertir tous les écouteurs de notre cible que son état à changé. À noter que la méthode accepte également un argument de type Object, lequel sera distribué à tous les écouteurs notifiés; pratique pour indiquer quel attribut a été modifié, ou bien pour indiquer quels observateurs doivent réagir à ce changement.

L’observateur à présent:

import java.util.Observable;
import java.util.Observer;
 
public class Observateur implements Observer {
 
	public Observateur(Cible cible) {
		cible.addObserver(this);
	}
 
	public void update(Observable o, Object arg) {
		System.out.println("la cible a changé!");
	}
}

l’Observateur implémente à présent l’interface Observer, laquelle nous propose d’implémenter la méthode update. C’est l’équivalent de propertyChange que l’on a vu dans le premier exemple. C’est donc ici qu’on pourra écrire le code qui traitera le changement d’état de la cible. Sauf qu’en plus on aura accès à l’objet qui est à l’origine de la notification du changement d’état ainsi qu’à l’objet optionnel que nous propose notifyObservers.

Voilà ceci conclu mon premier article sur les patrons de conception, j’imagine qu’il y en aura d’autres au fur et à mesure de mes recherches.