Patrons conceptuels

Introduction

On appelle patrons conceptuels (ou « Design pattern « ) un arrangement éprouvé de classes afin de répondre à un problème classique. Un patron de conception est considéré comme un ensemble de bonnes pratiques dans le domaine du développement logiciel.

Les patrons de création

Singleton

Un singleton est une instance unique d’une classe dans un programme.

On utilise un singleton dans le cas où il est souhaitable ou obligatoire qu’une classe n’ait qu’une seule instance dans l’exécution du programme.

Afin de garder le contrôle sur l’utilisation de ce singleton (et ainsi éviter les problèmes de variables globales), le singleton doit avoir un point d’accès unique, traçable et documenté.

Java:

import java.util.List;
import java.util.ArrayList;

public class MonSingleton {

    static private MonSingleton instance;

    static public MonSingleton getInstance() {
        if (instance == null) {
            instance = new MonSingleton();
        }
        return instance;
    }

    private MonSingleton(){
        attribut1 = 0;
        attribut2 = new ArrayList<String>();
    }

    protected int attribut1;

    protected ArrayList<String> attribut2;

    public void setAttribut1(int aAttribut1) {
        attribut1 = aAttribut1;
    }

    public int getAttribut1() {
        return attribut1;
    }

    public void setAttribut2(List<String> aAttribut2) {
        attribut2 = new ArrayList<String> (aAttribut2);
    }

    public List<String> getAttribut2() {
        return new ArrayList<String> (attribut2);
    }

}

Les trois (3) éléments importants de cet exemple sont les trois (3) premiers attributs et méthodes de la classe.

En premier lieu, l’attribut privé statique « instance » permet de placer l’instance unique de la classe « MonSingleton » dans un attribut statique. Un attribut statique représente la même valeur, peu importe d’où elle est accédé. Ça permet également d’utiliser l’attribut à partir d’une méthode statique.

La méthode publique statique « getInstance » permet aux autres classes du système d’accéder à l’instance du singleton sans nécessairement permettre l’accès global de l’attribut « instance » (afin d’éviter qu’une autre classe y assigne une valeur). On peut également voir que la méthode « getInstance » regarde si l’instance existe avant de le retourner et si l’instance n’existe pas, il le crée. On appelle cette technique le « Lazy Loading ». Il est également important de noter qu’il n’est pas nécessaire d’avoir une instance de classe pour lancer une méthode statique. Il est possible de faire un appel de méthode, appliqué directement sur la classe.

Finalement, on voit que le constructeur de la classe « MonSingleton » est déclaré comme privé. Cette fonctionnalité permet de nous assurer que le constructeur ne sera pas lancé à partir d’une autre classe que la classe en cours (dans ce cas, « MonSingleton »).

On utilise le singleton de la manière suivante:

public class Programme {

    static public void main(String[] args) {
        MonSingleton monSingleton = MonSingleton.getInstance();
        monSingleton.setAttribut1(10);
    }

}

Eiffel:

class
	MON_SINGLETON

create {MON_SINGLETON_SHARED}
	make

feature {NONE} -- Initialisation

	make
		do
			attribut1 := 0
			create {ARRAYED_LIST[STRING]} internal_attribut2.make (0)
		end

feature -- Access

	attribut1:INTEGER assign set_attribut1

	set_attribut1(a_attribut1:INTEGER)
		do
			attribut1 := a_attribut1
		end

	attribut2:LIST[STRING] assign set_attribut2
		do
			Result := internal_attribut2.twin
		end

	set_attribut2(a_attribut2:LIST[STRING])
		do
			create {ARRAYED_LIST[STRING]}
				internal_attribut2.make_from_iterable (a_attribut2)
		end

feature {NONE} -- Implémentation

	internal_attribut2:LIST[STRING]

end

La classe « MON_SINGLETON » est une classe complètement standard. La seule et unique particularité à observer est l’utilisation d’un constructeur ami avec la classe « MON_SINGLETON_SHARED »:

create {MON_SINGLETON_SHARED}
	make

La classe permettant le point d’accès au singleton est la classe « MON_SINGLETON_SHARED » qui est faite de cette manière:

deferred class
	MON_SINGLETON_SHARED

feature {NONE} -- Implementation

	mon_singleton:MON_SINGLETON
		once
			create Result.make
		end

end

La particularité ici, est le mot clé « once » qui agit d’une manière similaire au « static » de java (pour les méthodes seulement, pas les attributs). Ce qu’une méthode « once » fait, c’est qu’elle exécute la méthode une seule fois dans le programme et si cette méthode retourne une valeur, donc la valeur retournée sera toujours la même.

Finalement, pour accéder au singleton, il ne reste qu’à hériter de la classe « MON_SINGLETON_SHARED » et d’utiliser la méthode « mon_singleton ». Par exemple:

class
	APPLICATION

inherit
	MON_SINGLETON_SHARED

create
	make

feature {NONE} -- Initialisation

	make
			-- Exécute l'application.
		do
			mon_singleton.set_attribut1 (10)
		end

end

À noter à propos des singletons

    • Il est important de prendre en note que l’instance créée dans le cas d’un singleton sera en mémoire à partir de sa première utilisation et pour l’entièreté de l’exécution du programme. En d’autres mots, le ramasse-miette (« garbage collector ») ramassera seulement cet objet à la fermeture du programme.
      • Cette note est particulièrement importante dans le cas où le contenue du singleton est très lourd en mémoire et peu utile.
      • Cette note peut également être importante dans le cas où le singleton utilise des ressources externes (fichiers, libraires chargées dynamiquement, etc.) qui nécessitent d’être libéré avant la fin de l’exécution du programme.
    • Une autre note importante est à propos de l’utilisation des singletons dans un contexte multithread. Puisque chaque objet en exécution partage le même singleton, le risque de conflit augmente significativement.
    • Enfin, il est important de comprendre que chaque langage a une manière propre qui permet de gérer les singletons. Mais dans le cas où il n’y a aucune manière de créer des singletons dans un langage, un singleton peut également être un objet créé dans le « Main » du programme et envoyé en attribut aux constructeurs de tous les objets du système.

Fabrique

Une fabrique (ou « factory« ) est une classe (généralement un singleton), permettant la création de différents types d’objet. Une fabrique peut être utile lorsqu’on veut cacher l’implémentation de la création d’un certain type d’objet, ou bien qu’on veut éviter la duplication d’objet dans le système. Exemple:

Java:

public class BlocFabrique {

    public BlocFabrique() {
        imageBlocVert = new Image("image_bloc_vert.png");
        imageBlocRose = new Image("image_bloc_rose.png");
        imageBlocBleu = new Image("image_bloc_bleu.png");
        imageBlocJaune = new Image("image_bloc_jaune.png");
        imageBlocRouge = new Image("image_bloc_rouge.png");
        imageBlocMetal = new Image("image_bloc_metal.png");
    }

    public Bloc getBlocVert() {
        return new Bloc(imageBlocVert);
    }

    public Bloc getBlocRose() {
        return new Bloc(imageBlocRose);
    }

    public Bloc getBlocBleu() {
        return new Bloc(imageBlocBleu);
    }

    public Bloc getBlocJaune() {
        return new Bloc(imageBlocJaune);
    }

    public Bloc getBlocRouge() {
        return new Bloc(imageBlocRouge);
    }

    public Bloc getBlocMetal() {
        return new MetalBloc(imageBlocMetal);
    }

    private Image imageBlocVert;

    private Image imageBlocRose;

    private Image imageBlocBleu;

    private Image imageBlocJaune;

    private Image imageBlocRouge;

    private Image imageBlocMetal;

}

Eiffel:

class
	BLOC_FABRIQUE

create
	make

feature {NONE} -- Initialisation

	make
		do
			create image_bloc_vert.make ("image_bloc_vert.png")
			create image_bloc_rose.make ("image_bloc_rose.png")
			create image_bloc_bleu.make ("image_bloc_bleu.png")
			create image_bloc_jaune.make ("image_bloc_jaune.png")
			create image_bloc_rouge.make ("image_bloc_rouge.png")
			create image_bloc_metal.make ("image_bloc_metal.png")
		end

feature -- Accès

	bloc_vert:BLOC
		do
			create Result.make(image_bloc_vert)
		end

	bloc_rose:BLOC
		do
			create Result.make(image_bloc_rose)
		end

	bloc_bleu:BLOC
		do
			create Result.make(image_bloc_bleu)
		end

	bloc_jaune:BLOC
		do
			create Result.make(image_bloc_jaune)
		end

	bloc_rouge:BLOC
		do
			create Result.make(image_bloc_rouge)
		end

	bloc_metal:BLOC
		do
			create Result.make(image_bloc_metal)
		end

feature {NONE} -- Implémentation

	image_bloc_vert:IMAGE

	image_bloc_rose:IMAGE

	image_bloc_bleu:IMAGE

	image_bloc_jaune:IMAGE

	image_bloc_rouge:IMAGE

	image_bloc_metal:IMAGE

end

Ces exemples sont pris d’un jeu Arkanoid. Dans ces exemples, on présume qu’une image est très lourde en mémoire RAM, donc il est préférable de ne pas dupliquer ces images. Par contre, il est illogique de placer ces images ailleurs que dans une classe spécialisée sur les « Bloc ». L’idéal serait probablement que la classe « Bloc » elle-même charge l’image, mais se faisant, l’image d’un bloc d’une certaine couleur sera dupliquée dans tous les blocs de cette couleur. En faisant une fabrique de blocs, on crée une classe spécialisée dans la création de blocs et peut s’occuper de ne pas dupliquer l’image de chaque couleur de blocs.

Dans l’exemple d’Eiffel précédent, on pourrait également s’assurer que seule la classe {FABRIQUE_BLOC} peut créer un {BLOC} en rendant la classe {BLOC} ami avec {FABRIQUE_BLOC} (voir mécanique dans le singleton).

Fabrique abstraite

Lorsque le programme gère plusieurs variantes différentes des mêmes objets, on peut utiliser une fabrique abstraite (ou « abstract factory« ) afin d’éviter au programmeur d’avoir à se soucier du type d’objet à gérer.

Monteur

Un monteur (ou « builder« ) est une classe qui permet d’assigner certains attributs d’un objet avant de le créer. Souvent, un monteur est utilisé lorsqu’un objet doit être initialisé directement avec ses attributs préassignés, mais qu’en utilisant seulement des arguments du constructeur, ces derniers seraient trop complexes.

Les patrons structurels

Adaptateur

Un adaptateur (ou « Adapter« ) permet de faire fonctionner ensemble deux objets ayant des interfaces différentes, mais permettant un travail similaire. Par exemple, nous allons développer les classes suivantes:

L’objectif de la classe Adaptateur_Jouet, est de permettre à un Jouet d’être utilisé comme un Oiseau. Voici l’implémentation de ces classes.

Java:

public abstract class Oiseau {

    public abstract void crier();

}
public class Corneille extends Oiseau {

    public void crier() {
        System.out.println("Haar Haar");
    }
}
public class Merle extends Oiseau {

    public void crier() {
        System.out.println("Pit Pit");
    }
}
public abstract class Jouet {

    public abstract void ecraser();

}
public class CanardDeBain extends Jouet {
    
    public void ecraser() {
        System.out.println("Squick Squick");
    }

}
public class AdaptateurJouet extends Oiseau {

    private Jouet jouet;

    public AdaptateurJouet(Jouet aJouet) {
        jouet = aJouet;
    }

    public void crier() {
        jouet.ecraser();
    }

}

Et voici un exemple d’utilisation de l’adaptateur:

import java.util.List;
import java.util.ArrayList;

public class Programme {

    static public void main(String[] args) {
		List<Oiseau> oiseaux = new ArrayList<Oiseau>(3);
		Jouet canard = new CanardDeBain();
		oiseaux.add(new Corneille());
		oiseaux.add(new Merle());
		oiseaux.add(new AdaptateurJouet(canard));
		for(Oiseau oiseau : oiseaux) {
			oiseau.crier();
		}
    }

}

Eiffel:

deferred class
	OISEAU

feature -- Accès

	crier
		deferred
		end

end
class
	CORNEILLE

inherit
	OISEAU

feature -- Accès

	crier
		do
			print("Haar Haar")
		end

end
class
	MERLE

inherit
	OISEAU

feature -- Accès

	crier
		do
			print("Pit Pit")
		end

end
deferred class
	JOUET

feature -- Accès

	ecraser
		deferred
		end

end
class
	CANARD_DE_BAIN

inherit
	JOUET

feature -- Accès

	ecraser
		do
			print("Squick Squick")
		end

end
class
	ADAPTATEUR_JOUET

inherit
	OISEAU

create
	make

feature {NONE} -- Initialisation

	make(a_jouet:JOUET)
		do
			jouet := a_jouet
		end

feature -- Accès

	crier
		do
			jouet.ecraser
		end

feature {NONE} -- Implementation

	jouet:JOUET

end

Pour utiliser l’adaptateur:

class
	APPLICATION

create
	make

feature {NONE} -- Initialisation

	make
			-- Exécute l'application.
		local
			oiseaux:LIST[OISEAU]
			canard:CANARD_DE_BAIN
		do
			create {ARRAYED_LIST[OISEAU]} oiseaux.make(3)
			create canard
			oiseaux.extend (create {CORNEILLE})
			oiseaux.extend (create {MERLE})
			oiseaux.extend (create {ADAPTATEUR_JOUET}.make(canard))
			across oiseaux as la_oiseaux loop
				la_oiseaux.item.crier
				io.put_new_line
			end
		end

end

Lors de l’exécution de ce code, on obtient le résultat suivant:

Haar Haar
Pit Pit
Squick Squick

Décorateur

Un décorateur (ou « decorator« ) permet d’ajouter des fonctionnalités à une classe, sans avoir à modifier cette classe directement. Ce type de patron peut être utile, par exemple, dans le cas où l’on ne voulait pas avoir à modifier tout le code d’un programme à cause d’une modification d’une classe. Également, ce type de patron peut être utile pour s’assurer qu’une classe n’ait pas trop de responsabilités. Par exemple, voyez le diagramme suivant:

L’objectif de ce « desing » est d’ajouter un menu à une Page Web, sans nécessairement avoir à implanter ce menu dans la classe de la page Web (donc, en gardant cette dernière simple).

public class PageWeb {

    public String getTexte() {
        return "Le texte de la page Web";
    }

}
public abstract class DecorateurPage {

	private PageWeb page;

	public DecorateurPage(PageWeb aPage) {
		page = aPage;
	}

	public String getTexte() {
		return page.getTexte();
	}

}
public class DecorateurPageMenu extends DecorateurPage {
	
	public DecorateurPageMenu(PageWeb aPage) {
		super(aPage);
	}

	public String getTexte() {
		String textePage = super.getTexte();
		String resultat;
		// Ajouter le menu dans le textePage et mettre le résultat dans resultat.
		return resultat;
	}
}
public class Programme {

    static public void main(String[] args) {
		DecorateurPage page = new DecorateurPageMenu(new PageWeb());
		System.out.println(page.getTexte());
    }

}

Eiffel:

class
	PAGE_WEB

feature -- Accès

	texte:STRING
		do
			Result := "Le texte de la page web."
		end

end
deferred class
	DECORATEUR_PAGE

feature {NONE} -- Initialisation

	make(a_page:PAGE_WEB)
		do
			page := a_page
		end

feature -- Accès

	texte:STRING
		do
			Result := page.texte
		end

feature {NONE} -- Implémentation

	page:PAGE_WEB
	
end
class
	DECORATEUR_PAGE_MENU

inherit
	DECORATEUR_PAGE
		redefine
			texte
		end

create
	make	-- Le constructeur de cette classe est le make de {DECORATEUR_PAGE}

feature -- Accès

	texte:STRING
		local
			l_texte_page:STRING
		do
			l_texte_page := Precursor
			-- Ajouter le menu dans le texte l_texte_page et mettre le résultat dans Result
		end

end
class
	APPLICATION

create
	make

feature {NONE} -- Initialisation

	make
			-- Exécuter l'application.
		local
			l_page:DECORATEUR_PAGE
		do
			create {DECORATEUR_PAGE_MENU} l_page.make (create {PAGE_WEB})
			print(l_page.texte)
			io.put_new_line
		end

end

Modèle-Vue-Controlleur (MVC)

Le principe du patron MVC est de séparer les classes en 3 parties: les classes du modèle, les vues et les contrôleurs.

les classes du modèle

Les classes du modèle sont les classes directement reliées au domaine d’affaire que le programme permet une solution. Par exemple, si vous faites un programme pour un magasin, les classes du modèle seront des classes comme: Produit, Vendeur, Caisse, etc. Si vous faites un programme pour une école, les classes du modèle seront des classes comme: Étudiant, Enseignant, Classe, Bulletin, etc.

Les vues

Les vues sont toutes les classes qui gèrent l’interface graphique. Les classes de vues sont les classes qui ont la responsabilité de gérer des interfaces homme-machine comme: l’affichage (fenêtre, ligne de commande, etc.) ou les entrées (clavier, souris, bouton, case de texte, etc.) L’utilisateur manipule directement les classes de la vue.

Les contrôleurs

Les contrôleurs sont les classes contenant la logique applicative (les algorithmes de l’application). Ils font le lien entre les classes du modèle et les classes de la vue.

La séparation

L’important dans le patron MVC, c’est la séparation des différents types de classes (modèle, vue et contrôleur). Voici un diagramme d’interdépendance entre ces types:

Dans ce diagramme, les flèches représentent les dépendances entre les types de classes. Il faut y lire que la vue dépend du modèle seulement; les contrôleurs dépendent du modèle et de la vue et le modèle est complètement indépendant. Une autre façon de se le représenter, c’est que la vue « connaît » le modèle, mais pas le contrôleur; le contrôleur « connaît » le modèle et la vue et le modèle ne « connaît » rien d’autre que lui-même.

Patrons de comportement

Chaine de responsabilité

Une chaine de responsabilité permet de transmettre la responsabilité d’une gestion vers un autre objet en suivant une chaine ou un arbre d’objets interreliés entre eux. Par exemple:

On voit qu’il y a un principe parent/enfant dans une vue. À un endroit donné d’une Vue, il peut y avoir plusieurs Vues différentes (par exemple: une fenêtre, une grille de Vues et un Bouton). Donc, lorsque la Fenêtre reçoit une clique de souris, en plus de traiter lui-même ce clique, il va lancer le clique de ses Vues enfants.

Java:

import java.util.LinkedList;
import java.util.ArrayList;
import java.util.List;
import java.lang.IllegalArgumentException;

public abstract class Vue {

    private LinkedList<Vue> enfants;

    private Vue parent;

    public Vue(Vue aParent) {
        enfants = new LinkedList<Vue>();
        parent = aParent;
        if (parent != null) {
            parent.ajouterEnfant(this);
        }
    }

    public List<Vue> getEnfants() {
        return new ArrayList<Vue>(enfants);
    }

    public void ajouterEnfant(Vue aEnfant) {
        if (aEnfant.parent == this) {
            enfants.add(aEnfant);
        } else {
            throw new IllegalArgumentException();
        }
    }

}
public abstract class CliquableVue extends Vue{

    public CliquableVue(Vue aParent) {
       super(aParent);
    }

    public void gestionClique(int aX, int aY) {
        CliquableVue cliquableEnfant;
        for(Vue enfant : getEnfants()) {
            if (enfant instanceof CliquableVue) {
                cliquableEnfant = (CliquableVue) enfant;
                cliquableEnfant.gestionClique(aX, aY);
            }
        }
    }
}
public class Grille extends CliquableVue {

    public Grille(Vue aParent) {
        super(aParent);
    }

    @Override
    public void gestionClique(int aX, int aY) {
        super.gestionClique(aX, aY);
        // Gestion particulière du clique de souris pour la grille
    }

}
public class Fenetre extends CliquableVue {

    public Fenetre(Vue aParent) {
        super(aParent);
    }

    @Override
    public void gestionClique(int aX, int aY) {
        super.gestionClique(aX, aY);
        // Gestion particulière du clique de souris pour la Fenetre
    }

}
public class Bouton extends CliquableVue {

    public Bouton(Vue aParent) {
        super(aParent);
    }

    @Override
    public void gestionClique(int aX, int aY) {
        super.gestionClique(aX, aY);
        // Gestion particulière du clique de souris pour le bouton.
    }

}
public class Programme {

    static public void main(String[] args) {
        Fenetre fenetre = new Fenetre(null);
        Grille grille = new Grille(fenetre);
        Bouton bouton1 = new Bouton(grille);
        Bouton bouton2 = new Bouton(grille);
        fenetre.gestionClique(10, 10);
    }

}

Eiffel:

deferred class
	VUE

feature {NONE} -- Initilisation

	make(aParent:detachable VUE)
		do
			create {LINKED_LIST[VUE]} internal_enfants.make
			parent := aParent
			if attached parent as laParent then
				laParent.ajouter_enfant(Current)
			end
		end

feature -- Accès

	parent:detachable VUE

	enfants:LIST[VUE]
		do
			Result := internal_enfants.twin
		end

feature {VUE} -- Implémentation

	ajouter_enfant(a_enfant:VUE)
		require
			Is_Parent: a_enfant.parent = Current
		do
			internal_enfants.extend(a_enfant)
		end

feature {NONE} -- Implémentation

	internal_enfants:LIST[VUE]

end
deferred class
	CLIQUABLE_VUE

inherit
	VUE

feature -- Accès

	gestion_clique(a_x, a_y:INTEGER)
		do
			across enfants as la_enfants loop
				if attached {CLIQUABLE_VUE} la_enfants.item as la_enfant then
					la_enfant.gestion_clique(a_x, a_y)
				end
			end
		end

end
class
	FENETRE

inherit
	CLIQUABLE_VUE
		redefine
			gestion_clique
		end

create
	make

feature -- Accès

	gestion_clique(a_x, a_y:INTEGER)
		do
			Precursor(a_x, a_y)
			-- Gestion particulière du clique de souris pour la fenêtre.
		end

end
class
	GRILLE

inherit
	CLIQUABLE_VUE
		redefine
			gestion_clique
		end

create
	make

feature -- Accès

	gestion_clique(a_x, a_y:INTEGER)
		do
			Precursor(a_x, a_y)
			-- Gestion particulière du clique de souris pour le bouton.
		end

end
class
	BOUTON

inherit
	CLIQUABLE_VUE
		redefine
			gestion_clique
		end

create
	make

feature -- Accès

	gestion_clique(a_x, a_y:INTEGER)
		do
			Precursor(a_x, a_y)
			-- Gestion particulière du clique de souris pour le bouton.
		end

end
class
	APPLICATION

create
	make

feature {NONE} -- Initialisation

	make
			-- Exécuter l'application.
		local
			fenetre:FENETRE
			grille:GRILLE
			bouton1, bouton2:BOUTON
		do
			create fenetre.make (Void)
			create grille.make (fenetre)
			create bouton1.make (grille)
			create bouton2.make (grille)
			fenetre.gestion_clique (10, 10)
		end

end

Itérateur

Un itérateur permet d’itérer sur une structure de donnée sans avoir besoin de se soucier le la mécanique interne de la structure.

Médiateur

Un médiateur (ou « mediator« ) permet de transférer les messages entre différentes classes. Ce type de classe permet de diminuer l’interdépendance entre les différentes classes. En effet, en général, on essaie de diminuer l’interdépendance des classes (souvent appelé le principe du faible couplage), afin d’optimiser la réutilisabilité.

Si dans mon projet, j’ai par exemple une fenêtre et un produit. Dans la fenêtre, il y a un champ texte permettant de modifier le nom du produit et un bouton OK permettant d’accepter la modification du nom du produit. De son côté, le produit a une méthode « change_nom » permettant de modifier le nom du produit qui vérifie que le nom est valide. Si le nom n’est pas valide, le produit doit en informer la fenêtre afin que cette dernière affiche un message d’erreur pour l’utilisateur.

On pourrait représenter cet exemple avec le diagramme de classe suivant:

Par contre, ici, la fenêtre et le produit deviennent dépendants un de l’autre puisque la fenêtre nécessite le produit pour lancer sa méthode « changer_nom » et le produit nécessite la fenêtre pour lancer la méthode « erreur_nom ».

En utilisant un médiateur, on peut s’assurer de garder l’interdépendance entre les deux classes. Voici un exemple:

Java:

public class Fenetre {

    public Fenetre() {
        texte = "";
    }

    private String texte;

    public void setTexte(String aTexte) {
        texte = aTexte;
    }

    public String getTexte() {
        return texte;
    }

    public void boutonClique() {
        if (mediateur != null) {
            mediateur.changeTexte(texte);
        }
    }

    public void setMediateur(MediateurFenetre aMediateur) {
        mediateur = aMediateur;
    }

    public void erreurTexte() {
        System.out.println("Le texte n'est pas valide.");
    }

    private MediateurFenetre mediateur;

}
public class Produit {

    public Produit() {
        nom = "";
    }

    private String nom;

    public String getNom() {
        return nom;
    }

    public void changeNom(String aNom) {
        if (aNom == null || aNom.isEmpty()) {
            mediateur.erreurNom();
        } else {
            nom = aNom;
        }
    }

    public void setMediateur(MediateurProduit aMediateur) {
        mediateur = aMediateur;
    }

    MediateurProduit mediateur;

}
public interface MediateurFenetre {

    public void changeTexte(String aTexte);

}
public interface MediateurProduit {

    public void erreurNom();

}
public class MediateurFenetreProduit implements MediateurFenetre, MediateurProduit {

    public MediateurFenetreProduit(Fenetre aFenetre, Produit aProduit) {
        fenetre = aFenetre;
        produit = aProduit;
        fenetre.setMediateur(this);
        produit.setMediateur(this);
    }

    private Fenetre fenetre;

    private Produit produit;

    public void erreurNom() {
        fenetre.erreurTexte();
    }

    public void changeTexte(String aTexte) {
        produit.changeNom(aTexte);
    }

}

Eiffel:

class
	FENETRE

create
	make

feature {NONE} -- Initialisation

	make
		do
			texte := ""
		end

feature -- Accès

	texte:STRING assign set_texte

	set_texte(a_texte:STRING)
		do
			texte := a_texte
		end

	bouton_clique
		do
			if attached mediateur as la_mediateur then
				la_mediateur.change_texte(texte)
			end
		end

	erreur_texte
		do
			print("Le texte n'est pas valide.%N")
		end

	mediateur:detachable MEDIATEUR_FENETRE assign set_mediateur

	set_mediateur(a_mediateur:detachable MEDIATEUR_FENETRE)
		do
			mediateur := a_mediateur
		end

end
class
	PRODUIT

create
	make

feature {NONE} -- Initialisation

	make
		do
			nom := ""
		end

feature -- Accès

	nom:STRING

	change_nom(a_nom:STRING)
		do
			if attached mediateur as la_mediateur then
				la_mediateur.erreur_nom
			end
		end

	mediateur:detachable MEDIATEUR_PRODUIT assign set_mediateur

	set_mediateur(a_mediateur:detachable MEDIATEUR_PRODUIT)
		do
			mediateur := a_mediateur
		end

end
deferred class
	MEDIATEUR_FENETRE

feature -- Accès

	change_texte(a_texte:STRING)
		deferred
		end

end
deferred class
	MEDIATEUR_PRODUIT

feature -- Accès

	erreur_nom
		deferred
		end
		
end
class
	MEDIATEUR_FENETRE_PRODUIT

inherit
	MEDIATEUR_FENETRE
	MEDIATEUR_PRODUIT

create
	make

feature {NONE} -- Initialisation

	make(a_fenetre:FENETRE; a_produit:PRODUIT)
		do
			fenetre := a_fenetre
			produit := a_produit
			fenetre.set_mediateur (Current)
			produit.set_mediateur (Current)
		end

feature -- Accès

	fenetre:FENETRE

	produit:PRODUIT

	change_texte(a_texte:STRING)
		do
			produit.change_nom(a_texte)
		end

	erreur_nom
		do
			fenetre.erreur_texte
		end

end
class
	APPLICATION

create
	make

feature {NONE} -- Initialisation

	make
			-- Exécute l'application.
		local
			produit:PRODUIT
			fenetre:FENETRE
			mediateur:MEDIATEUR_FENETRE_PRODUIT
		do
			create fenetre.make
			create produit.make
			create mediateur.make (fenetre, produit)
			fenetre.bouton_clique
		end

end

On voit que dans ce cas, nous pourrions facilement mettre les classes « Fenetre » et « MediateurFenetre » dans un « package », « dépôt », « librairie », etc. et faire de même avec « Produit » et « MediateurProduit ». Ils sont donc rendus indépendants. Lorsque l’utilisateur veut utiliser la classe « Produit » ou « Fenetre », il doit se créer une classe médiatrice qui hérite (implémente dans le cas de java) du médiateur respectif (« MediateurProduit » ou « MediateurFenetre »).

Mémento

Un mémento (ou « memento« ) permet de garder l’état d’un objet pour pouvoir retourner à cet état dans le futur. Ce type de mécanique est généralement utile lorsque le logiciel nécessite la fonctionnalité de « défaire » et de « refaire » (« undo » et « redo »).

Le principe du Mémento est le suivant:

L’objet à sauvegarder contient des attributs représentants l’état de l’objet à un instant donné (par exemple, ici, c’est le texte et le curseur). Ensuite, on permet de créer des objets Mémento contenant une copie de l’état à partir de l’objet qu’on veut permettre de défaire et refaire les modifications (ici, la boite texte). Un autre objet Gardien permet de sauvegarder et de retourner les différents Mémentos. Finalement, l’objet qu’on veut permettre de défaire et refaire les modifications doit également avoir une méthode pour remettre son état à l’état d’un mémento.

Java:

public class BoiteTexte {

    private String texte;

    private int curseur;

    public BoiteTexte() {
        setTexte("");
        setCurseur(0);
    }

    public void setTexte(String aTexte) {
        texte = aTexte;
    }

    public String getTexte() {
        return texte;
    }

    public void setCurseur(int aCurseur) {
        curseur = aCurseur;
    }

    public int getCurseur() {
        return curseur;
    }

    public TexteMemento creerMemento() {
        return new TexteMemento(new String(texte), curseur);
    }

    public void setMemento(TexteMemento aMemento) {
        setTexte(new String(aMemento.getTexte()));
        setCurseur(aMemento.getCurseur());
    }

}
public class TexteMemento {

    private String texte;

    private int curseur;

    public String getTexte() {
        return texte;
    }

    public int getCurseur() {
        return curseur;
    }

    public TexteMemento(String aTexte, int aCurseur) {
        texte = aTexte;
        curseur = aCurseur;
    }

}
import java.util.List;
import java.util.LinkedList;

public class Gardien {
    
    private List<TexteMemento> etats;

    public List<TexteMemento> getEtats() {
        return etats;
    }

    private int index;

    public int getIndex() {
        return index;
    }

    public Gardien() {
        etats = new LinkedList<TexteMemento>();
        index = -1;
    }

    public void ajouterMemento(TexteMemento aMemento) {
        if (index < etats.size() - 1){
            etats = etats.subList(0, index + 1);
        }
        etats.add(aMemento);
        index = index + 1;
    }

    public TexteMemento getMemento() {
        TexteMemento lMemento = null;
        if (index < etats.size() && index > -1) {
            lMemento = etats.get(index);
        } else {
            throw new IndexOutOfBoundsException("Index invalid");
        }
        return etats.get(index);
    }

    public void precedent() {
        if (index > 0) {
            index = index - 1;
        } else {
            throw new IndexOutOfBoundsException("Index invalid");
        }
    }

    public void suivant() {
        if (index < etats.size()) {
            index = index + 1;
        } else {
            throw new IndexOutOfBoundsException("Index invalid");
        }
    }

}
public class Programme {

    static public void main(String[] args) {
        BoiteTexte boiteTexte = new BoiteTexte();
        Gardien gardien = new Gardien();

        boiteTexte.setTexte("État 1");
        boiteTexte.setCurseur(1);
        gardien.ajouterMemento(boiteTexte.creerMemento());
        boiteTexte.setTexte("État 2");
        boiteTexte.setCurseur(2);
        gardien.ajouterMemento(boiteTexte.creerMemento());
        boiteTexte.setTexte("État 3");
        boiteTexte.setCurseur(3);
        gardien.ajouterMemento(boiteTexte.creerMemento());
        gardien.precedent();
        boiteTexte.setTexte("État 4");
        boiteTexte.setCurseur(4);
        gardien.ajouterMemento(boiteTexte.creerMemento());
        boiteTexte.setTexte("État 5");
        boiteTexte.setCurseur(5);
        gardien.ajouterMemento(boiteTexte.creerMemento());
        gardien.precedent();
        boiteTexte.setMemento(gardien.getMemento());
        System.out.println("Texte: " + boiteTexte.getTexte() +
                " Curseur: " + boiteTexte.getCurseur());
    }

}

Eiffel:

class
	BOITE_TEXTE

create
	make

feature {NONE} --Initialisation

	make
		do
			texte := ""
			curseur := 0
		end

feature -- Accès

	creer_memento:TEXTE_MEMENTO
		do
			create Result.make(texte.twin, curseur)
		end

	set_memento(a_memento:TEXTE_MEMENTO)
		do
			set_texte(a_memento.texte)
			set_curseur(a_memento.curseur)
		end

	texte:STRING assign set_texte

	set_texte(a_texte:STRING)
		do
			texte := a_texte
		end

	curseur:INTEGER assign set_curseur

	set_curseur(a_curseur:INTEGER)
		do
			curseur := a_curseur
		end

end
class
	TEXTE_MEMENTO

create {BOITE_TEXTE}
	make

feature {NONE} --Initialisation

	make(a_texte:STRING; a_curseur:INTEGER)
		do
			texte := a_texte
			curseur := a_curseur
		end

feature {BOITE_TEXTE} -- Implementation

	texte:STRING

	curseur:INTEGER

end
class
	GARDIEN

create
	make

feature {NONE} -- Initialisation

	make
		do
			create {LINKED_LIST[TEXTE_MEMENTO]}etats.make
			index := 0
		end

feature -- Accès

	etats:LIST[TEXTE_MEMENTO]

	index:INTEGER

	ajouter_memento(a_memento:TEXTE_MEMENTO)
		do
			from
			until
				index >= etats.count
			loop
				etats.finish
				etats.remove
			end
			etats.extend (a_memento)
			index := index + 1
		end

	memento:TEXTE_MEMENTO
		require
			Index_Valid: index >= 1 and index <= etats.count
		do
			Result := etats.at (index)
		end

	precedent
		require
			Index_Valid: index > 1
		do
			index := index - 1
		end

	suivant
		require
			Index_Valid: index < etats.count
		do
			index := index + 1
		end

end
class
	APPLICATION

create
	make

feature {NONE} -- Initialization

	make
			-- Initialisation de `Current'
		local
			l_boite_texte:BOITE_TEXTE
			l_gardien:GARDIEN
		do
			create l_boite_texte.make
			create l_gardien.make
			l_boite_texte.set_texte ("Etat 1")
			l_boite_texte.set_curseur (1)
			l_gardien.ajouter_memento (l_boite_texte.creer_memento)
			l_boite_texte.set_texte ("Etat 2")
			l_boite_texte.set_curseur (2)
			l_gardien.ajouter_memento (l_boite_texte.creer_memento)
			l_boite_texte.set_texte ("Etat 3")
			l_boite_texte.set_curseur (3)
			l_gardien.ajouter_memento (l_boite_texte.creer_memento)
			l_gardien.precedent
			l_boite_texte.set_texte ("Etat 4")
			l_boite_texte.set_curseur (4)
			l_gardien.ajouter_memento (l_boite_texte.creer_memento)
			l_boite_texte.set_texte ("Etat 5")
			l_boite_texte.set_curseur (5)
			l_gardien.ajouter_memento (l_boite_texte.creer_memento)
			l_gardien.precedent
			l_boite_texte.set_memento (l_gardien.memento)
			print("Texte: " + l_boite_texte.texte + " Curseur: " +
					l_boite_texte.curseur.out + "%N")
		end

end

Dans les exemples précédents, le résultat est:

Texte: État 4 Curseur: 4

Observeur

Le patron observeur (ou « observer« ) permet à plusieurs objets de type « observeur » d’observer un objet de type « observable« . Dès que l’état de l’objet observable change, les objets de type « observeur » en sont immédiatement informés. De plus, il n’est pas nécessaire que les objets de type « observable » aient de dépendance envers les objets de type « observeur ». En d’autre mots, l’objet de type « observable » n’a pas à « connaitre » les « observeur » pour que le patron fonctionne. Par exemple:

Java:

import java.util.List;
import java.util.LinkedList;

public abstract class Observable {

    private List<Observeur> observeurs;

    public Observable() {
        observeurs = new LinkedList<Observeur>();
    }

    public void attache(Observeur aObserveur) {
        observeurs.add(aObserveur);
    }

    public void detache(Observeur aObserveur) {
        observeurs.remove(aObserveur);
    }

    protected void informe() {
        for(Observeur laObserveur : observeurs) {
            laObserveur.actualisation(this);
        }
    }

}
public interface Observeur {
    
    public void actualisation(Observable observable);

}
public class Produit extends Observable {


    public Produit() {
        super();
        nom = "";
    }

    private String nom;

    public String getNom() {
        return nom;
    }

    public void setNom(String aNom) {
        nom = aNom;
		informe();
    }

    private int prix;

    public int getPrix() {
        return prix;
    }

    public void setPrix(int aPrix) {
        prix = aPrix;
		informe();
    }

}
public class AfficheurProduit implements Observeur {

    public AfficheurProduit() {
        texte = "";
    }

    private String texte;

    public String getTexte() {
        return texte;
    }

    public void actualisation(Observable aObservable) {
        Produit lProduit = (Produit)aObservable;
        texte = "Le produit " + lProduit.getNom() + " au cout de " + lProduit.getPrix();
    }

}
public class Programme {

    static public void main(String[] args) {
		AfficheurProduit afficheur = new AfficheurProduit();
		Produit produit = new Produit();
		produit.attache(afficheur);
		produit.setNom("Ordinateur");
		produit.setPrix(200);
		System.out.println(afficheur.getTexte());
    }

}

Il est important de noter que la mécanique Observeur est déjà dans les librairies Java. Par contre, considérant que Java ne gère pas l’héritage multiple, il est parfois nécessaire de coder la mécanique de l’observable à dans la classe qui doit être observable. Voir: https://docs.oracle.com/javase/7/docs/api/java/util/Observer.html

Eiffel:

deferred class
	OBSERVABLE

feature {NONE} -- Initialisation

	make
		do
			create {LINKED_LIST[OBSERVEUR]} observeurs.make
		end

feature -- Accès

	attache(a_observeur:OBSERVEUR)
		do
			observeurs.extend (a_observeur)
		end

	detache(a_observeur:OBSERVEUR)
		do
			observeurs.prune_all (a_observeur)
		end

	informe
		do
			observeurs.do_all (
				agent {OBSERVEUR}.actualisation(Current))
		end

feature {NONE} -- Implémentaition

	observeurs:LIST[OBSERVEUR]

end
deferred class
	OBSERVEUR

feature {OBSERVABLE} -- Implémentation

	actualisation(a_observable:OBSERVABLE)
		deferred
		end

end

Grâce à la mécanique de classe ami d’Eiffel. On voit qu’on peut retreindre les appels de la méthode « actualisation » aux classes « OBSERVABLE » seulement. Celà permet d’éviter que cette méthode soit utilisé à l’extérieur du contexte du patron « Observeur ».

class
	PRODUIT

inherit
	OBSERVABLE
		rename
			make as make_observable
		end

create
	make

feature {NONE} -- Initialisation

	make
		do
			make_observable
			nom := ""
		end

feature -- Accès

	nom:STRING assign set_nom

	set_nom(a_nom:STRING)
		do
			nom := a_nom
			informe
		end

	prix:INTEGER assign set_prix

	set_prix(a_prix:INTEGER)
		do
			prix := a_prix
			informe
		end

end
class
	AFFICHEUR_PRODUIT

inherit
	OBSERVEUR

create
	make

feature {NONE} -- Initialisation

	make
		do
			texte := ""
		end

feature -- Accès

	texte:STRING

feature {PRODUIT} -- Implémentation

	actualisation(a_produit:PRODUIT)
		do
			texte := "Le produit " + a_produit.nom + " au prix de " + a_produit.prix.out
		end

end

Ici, on peut voir que lors d’une redéfinition en Eiffel, on peut modifier le type d’un argument, en autant que le nouvel argument soit de type descendant du type d’origine.

class
	APPLICATION

create
	make

feature {NONE} -- Initialisation

	make
			-- Exécute l'application.
		local
			l_produit:PRODUIT
			l_afficheur:AFFICHEUR_PRODUIT
		do
			create l_produit.make
			create l_afficheur.make
			l_produit.attache (l_afficheur)
			l_produit.nom := "Ordinateur"
			l_produit.prix := 200
			print(l_afficheur.texte + "%N")
		end

end

Visiteur

Le patron visiteur (ou « visitor« )permet d’ajouter un algorithme qui s’applique à une classe en particulier, mais sans avoir à modifier les méthodes de la classe. Il est généralement préférable de mettre la méthode dans la classe en tant que telle; mais il peut arriver que ce type de patron soit avantageux. Par exemple, pour garder la classe simple d’utilisation; ou bien dans le cas ou il est impossible de mettre une méthode dans la classe (par exemple, dans une interface en langage objet avec héritage simple seulement).

Java:

public interface ElementComptable {

    public void accepte(ElementComptableVisiteur visiteur);

}
public class Produit implements ElementComptable {

    private String nom;

    private int prix;

    public Produit() {
        nom = "";
    }

    public void setNom(String aNom) {
        nom = aNom;
    }

    public String getNom() {
        return nom;
    }

    public void setPrix(int aPrix) {
        prix = aPrix;
    }

    public int getPrix() {
        return prix;
    }

    public void accepte(ElementComptableVisiteur visiteur) {
        visiteur.visiteProduit(this);
    }

}
public class Client implements ElementComptable {

    private String nom;

    private String numeroTelephone;

    public Client() {
        nom = "";
        numeroTelephone = "";
    }

    public void setNom(String aNom) {
        nom = aNom;
    }

    public String getNom() {
        return nom;
    }

    public void setNumeroTelephone(String aNumeroTelephone) {
        numeroTelephone = aNumeroTelephone;
    }

    public String getNumeroTelephone() {
        return numeroTelephone;
    }

    public void accepte(ElementComptableVisiteur visiteur) {
        visiteur.visiteClient(this);
    }

}
public class Facture implements ElementComptable {

    private int total;

    private int numero;

    public void setTotal(int aTotal) {
        total = aTotal;
    }

    public int getTotal() {
        return total;
    }

    public void setNumero(int aNumero) {
        numero = aNumero;
    }

    public int getNumero() {
        return numero;
    }

    public void accepte(ElementComptableVisiteur visiteur) {
        visiteur.visiteFacture(this);
    }

}
public interface ElementComptableVisiteur {

    public void visiteProduit(Produit aProduit);

    public void visiteClient(Client aClient);

    public void visiteFacture(Facture aFacture);

}
public class Afficheur implements ElementComptableVisiteur {

    public void visiteProduit(Produit aProduit) {
        System.out.println("Le produit " + aProduit.getNom() + 
                " au prix de " + aProduit.getPrix() + "$");
    }

    public void visiteClient(Client aClient) {
        System.out.println("Le client appelé " + aClient.getNom() + 
                " qui peut être rejoint au " + aClient.getNumeroTelephone());
    }

    public void visiteFacture(Facture aFacture) {
        System.out.println("La facture numéro " + aFacture.getNumero() + 
                " totalise " + aFacture.getTotal() + "$");
    }

}
public class Programme {

    static public void main(String[] args) {
        Produit produit = new Produit();
        Client client = new Client();
        Facture facture = new Facture();
        Afficheur afficheur = new Afficheur();
        produit.setNom("Ordinateur");
        produit.setPrix(200);
        client.setNom("Louis M");
        client.setNumeroTelephone("555-123-4567");
        facture.setNumero(1);
        facture.setTotal(1000);
        produit.accepte(afficheur);
        client.accepte(afficheur);
        facture.accepte(afficheur);
    }

}

Eiffel:

deferred class
	ELEMENT_COMPTABLE

feature -- Accès

	accepte(a_visiteur:ELEMENT_COMPTABLE_VISITEUR)
		deferred
		end

end
class
	PRODUIT

inherit
	ELEMENT_COMPTABLE

create
	make

feature {NONE} -- Initialisation

	make
		do
			nom := ""
		end

feature -- Accès

	nom:STRING assign set_nom

	set_nom(a_nom:STRING)
		do
			nom := a_nom
		end

	prix:INTEGER assign set_prix

	set_prix(a_prix:INTEGER)
		do
			prix := a_prix
		end

	accepte(a_visiteur:ELEMENT_COMPTABLE_VISITEUR)
		do
			a_visiteur.visite_produit (Current)
		end

end
class
	CLIENT

inherit
	ELEMENT_COMPTABLE

create
	make

feature {NONE} -- Initialisation

	make
		do
			nom := ""
			numero_telephone := ""
		end

feature -- Accès

	nom:STRING assign set_nom

	set_nom(a_nom:STRING)
		do
			nom := a_nom
		end

	numero_telephone:STRING assign set_numero_telephone

	set_numero_telephone(a_numero_telephone:STRING)
		do
			numero_telephone := a_numero_telephone
		end

	accepte(a_visiteur:ELEMENT_COMPTABLE_VISITEUR)
		do
			a_visiteur.visite_client (Current)
		end

end
class
	FACTURE

inherit
	ELEMENT_COMPTABLE

feature -- Accès

	numero:INTEGER assign set_numero

	set_numero(a_numero:INTEGER)
		do
			numero := a_numero
		end

	total:INTEGER assign set_total

	set_total(a_total:INTEGER)
		do
			total := a_total
		end

	accepte(a_visiteur:ELEMENT_COMPTABLE_VISITEUR)
		do
			a_visiteur.visite_facture (Current)
		end

end
deferred class
	ELEMENT_COMPTABLE_VISITEUR

feature -- Accès

	visite_produit(a_produit:PRODUIT)
		deferred
		end

	visite_client(a_client:CLIENT)
		deferred
		end

	visite_facture(a_facture:FACTURE)
		deferred
		end

end
class
	AFFICHEUR

inherit
	ELEMENT_COMPTABLE_VISITEUR

feature -- Accès

	visite_produit(a_produit:PRODUIT)
		do
			print("Le produit " + a_produit.nom +
                  " au prix de " + a_produit.prix.out + "$%N")
		end

	visite_client(a_client:CLIENT)
		do
			print("Le client appelé " + a_client.nom +
                  " qui peut être rejoint au " + a_client.numero_telephone + "%N")
		end

	visite_facture(a_facture:FACTURE)
		do
			print("La facture numéro " + a_facture.numero.out +
                  " totalise " + a_facture.total.out + "$%N")
		end
end
class
	APPLICATION

create
	make

feature {NONE} -- Initialisation

	make
			-- Exécute l'application.
		local
			produit:PRODUIT
			client:CLIENT
			facture:FACTURE
			afficheur:AFFICHEUR
		do
			create produit.make
			create client.make
			create facture
			create afficheur
			produit.nom := "Ordinateur"
			produit.prix := 200
			client.nom := "Louis M"
			client.numero_telephone := "555-123-4567"
			facture.numero := 1
			facture.total := 1000
			produit.accepte (afficheur)
			client.accepte (afficheur)
			facture.accepte (afficheur)
		end

end

Les codes précédents donnent le résultat:

Le produit Ordinateur au prix de 200$
Le client appelé Louis M qui peut être rejoint au 555-123-4567
La facture numéro 1 totalise 1000$

Retour


Auteur: Louis Marchand
Creative Commons License
Sauf pour les sections spécifiées autrement, ce travail est sous licence Creative Commons Attribution 4.0 International.