Chapitre 4 - Design Pattern Gof Partie 2
Chapitre 4 - Design Pattern Gof Partie 2
Chapitre 4 - Design Pattern Gof Partie 2
Rôle:
Chapitre 3 la façon la plus classique d’ajouter des fonctionnalités à
Les Design Pattern GOF une classe est d’utiliser l’héritage. Pourtant il arrive
parfois de vouloir ajouter des fonctionnalités à une classe
sans utiliser l’héritage
Si l’on hérite d’une classe la redéfinition d’une méthode
peut entraîner l’ajout de nouveaux bugs. l’héritage doit
être utilisé avec parcimonie. En plus il est statique
Cours Architectures logicielle 1 Cours Architectures logicielle 2
Rôle:
Rôle:
D’une manière générale on constate que l’ajout de
fonctionnalités dans un programme s’avère parfois délicat et
complexe. Ce problème peut être résolu si le développeur a
identifié, dès la conception, qu’une partie de l’application
serait sujette à de fortes évolutions. Il peut alors faciliter ces
modifications en utilisant le pattern Décorateur. La puissance
de ce pattern qui permet d’ajouter (ou modifier) des
fonctionnalités facilement provient de la combinaison de
l’héritage et de la composition.
1
12/03/2017
Rôle: Rôle:
Tout d’abord on a une classe ComposantConcret qui Pour cela on peut créer un objet DécorateurConcret en
possède une méthode chargée d’une fonctionnalité. passant à son constructeur notre objet
Suivant l’objet créé on souhaite ajouter des traitements ComposantConcret (dont l’on souhaite étendre les
lors de l’appel de cette méthode. Cependant on ne doit fonctionnalités). On peut ensuite redéfinir la méthode
pas modifier directement le corps de la méthode car concernée et ajouter des traitements. On appelle la
certains objets utiliseront toujours l’ancienne version de méthode du ComposantConcret puis on rajoute des
cette méthode fonctionnalités
Cours Architectures logicielle 5 Cours Architectures logicielle 6
Rôle: Rôle:
On obtient un objet ComposantConcret qui est emballé Comme tous les patrons de conception, Décorateur ne
dans un DecorateurConcret. Ainsi si on appelle la
méthode sur l’objet décorateur, celle-ci va appeler la doit pas être utilisé à tord et à travers. Mais lors de la
méthode du composant concret, ajouter ses propres conception de classes qui risquent d’évoluer fortement
traitements et retourner le résultat. A noter, que si on (ajout ou modification de fonctionnalités) celui-ci sera
appelle directement la méthode de l’objet très utile. Il est donc important de bien réfléchir aux
ComposantConcret (sans passer par le décorateur) on points sensibles de l’application qui risquent d’évoluer
utilise alors l’ancienne version de la méthode au fil du temps et cela dès la phase d’analyse .
Cours Architectures logicielle 7 Cours Architectures logicielle 8
2
12/03/2017
Rôle: Rôle:
Attention tout de même à l’utilisation des types concrets.
Si votre application se base sur les types concrets d’objets De plus, lors de l’utilisation du pattern Décorateur, on
utilisés dans le pattern décorateur cela posera des constate qu’il est fastidieux de gérer tous les objets créés
problèmes. En effet, une fois décoré un et de les décorer. C’est pour cette raison que ce pattern
ComposantConcret aura pour type concret celui de son est souvent utilisé avec le pattern Fabrique ou Monteur
décorateur le plus externe. qui répondent à cette problématique
Exemple: Exemple:
afin de mettre en pratique le pattern Décorateur, nous Pour simplifier notre exemple, nous choisirons
allons concevoir une application qui permet de gérer la
vente de desserts. Celle-ci doit permettre d’afficher dans uniquement deux ingrédients (le chocolat et la chantilly)
la console le nom complet du dessert choisi et son prix. mais il faut garder à l’esprit que l’ajout de nouveaux
Les clients ont le choix entre deux desserts : crêpe ou ingrédients doit être simplifié. Le système de tarification
gaufre. Sur chaque dessert ils peuvent ajouter un nombre est simple. Une crêpe (nature) coûte 1.50TND et une
quelconque d’ingrédients afin de faire leurs propres gaufre 1.800TND. L’ajout de chocolat est facturé
assortiments 0.200TND et de chantilly 0.500 TND.
Cours Architectures logicielle 11 Cours Architectures logicielle 12
3
12/03/2017
Exemple: Exemple:
Voyons comment concevoir cette application. Une Bien sûr cette solution n’est pas évolutive. Si l’on
première idée est de mettre en place une classe abstraite souhaite modifier le prix de l’ingrédient Chocolat on doit
Dessert ayant deux attributs (libelle et prix) et les le modifier dans deux classes. De plus, si on ajoute une
accesseurs en lecture/écriture correspondants. Puis, dizaine d’ingrédients nous allons obtenir une centaine
créer pour chaque combinaison de desserts et de classes.
d’ingrédients une classe (CrepeChocolat,
CrepeChantilly, GaufreChocolat, GaufreChantilly)
Cours Architectures logicielle 13 Cours Architectures logicielle 14
Exemple: Exemple:
Une deuxième solution consiste à garder la classe Le calcul du prix des ingrédients est effectué dans la
Dessert en la modifiant légèrement. On peut rajouter un classe Dessert auquel on rajoute le prix spécifique du
booléen pour savoir si l’ingrédient chocolat est ajouté à dessert suivant le type de l’objet (Gaufre ou Crepe).
ce dessert de même pour Chantilly. Puis, on crée des Cette solution semble plus satisfaisante mais pose
classes Crepe et Gaufre qui héritent de Dessert. toujours certains problèmes.
4
12/03/2017
Exemple: Exemple:
Si l’on souhaite rajouter un ingrédient, il faut ajouter un
attribut dans la classe Dessert et modifier la méthode
qui calcule le prix afin de le prendre en considération.
De plus, toutes les classes héritant de Dessert
posséderont ces attributs qui n’auront pas toujours de
sens. Si on crée une classe SaladeDeFruit héritant de
Dessert on aura un attribut (hérité de la classe mère)
nommé chocolat (bizarre pour une salade de fruit)
Cours Architectures logicielle 17 Cours Architectures logicielle 18
Exemple: Exemple:
La solution consiste à utiliser à la fois l’héritage et la Celle-ci possède un Dessert en attribut et oblige la
composition. Une classe abstraite Dessert regroupe les redéfinition de deux méthodes getLibelle() et getPrix().
attributs et les méthodes communes. Puis, des desserts Chaque ingrédient (Chantilly, Chocolat...) doit hériter de la
concrets tel que Gaufre et Crepe héritent de cette classe. classe DecorateurIngredient. Le constructeur de ces classes
Dans le constructeur de ces classes on met à jour les permet d’initialiser l’attribut dessert présent dans la classe
attributs défini dans Dessert à l’aide des accesseurs. Afin mère. De plus, la redéfinition des méthodes getLibelle() et
de gérer les ingrédients, il faut une classe abstraite getPrix() va permettre d’ajouter des fonctionnalités. Pour
nommée DecorateurIngredient comprendre comment cela fonctionne voyons le code Java
Cours Architectures logicielle 19 Cours Architectures logicielle 20
5
12/03/2017
Exemple: Exemple:
Exemple: Exemple:
La classe abstraite Dessert possède deux attributs que
sont libelle et prix. Le premier correspond au nom du
dessert sélectionné et le deuxième au prix d’achat. Des
public String toString()
{
accesseurs en lecture/écriture permettent d’accéder à ces
NumberFormat format=NumberFormat.getInstance(); attributs. Afin de faciliter l’affichage dans la console,
format.setMinimumFractionDigits(3) nous avons implémenter la méthode toString(). Celle-ci
return getLibelle()+" : sera appelée automatiquement lors de l’affichage d’un
"+format.format(getPrix())+« TND";
} objet de type Dessert dans la console
Cours Architectures logicielle 23 Cours Architectures logicielle 24
6
12/03/2017
Exemple: Exemple:
Exemple:
Exemple:
DecorateurIngredient va permettre de décorer les desserts
Les classes Gaufre et Crepe sont des desserts concrets et avec différents ingrédients. Il s’agit d’une classe abstraite
donc héritent de Dessert. Leurs constructeurs héritant de dessert. Celle-ci possède en attribut le dessert
permettent de mettre à jour le libellé et le prix (grâce aux qu’elle va décorer (c’est à dire ajouter des fonctionnalités). A
accesseurs en écriture). On constate qu’il sera facile de noter, que ce dessert peut correspondre à un dessert déjà
décoré puisque celui-ci hérite indirectement de la classe
rajouter un nouveau dessert concret sans modifier notre Dessert. Le DecorateurIngredient oblige également la
modèle redéfinition des deux méthodes getLibelle() et getPrix() dans
ses sous classes
7
12/03/2017
Exemple: Exemple:
Exemple: Exemple:
8
12/03/2017
Exemple: Exemple:
Les classes Chantilly et Chocolat correspondent à deux Les méthodes getLibelle() et getPrix() sont redéfinies et
ingrédients qui peuvent être ajoutés aux desserts. Pour
cela ces classes héritent de DecorateurIngredient. Leurs ainsi on ajoute des fonctionnalités. Par exemple la
constructeurs prennent en paramètre le dessert « nature méthode getLibelle() affiche le libellé du dessert en
» qui sera stocké dans l’attribut de rajoutant le libellé de l’ingrédient. Le même principe est
DecorateurIngrédient. On note, que l’attribut dessert de utilisé pour la méthode getPrix() pour le calcul du prix.
la classe mère est déclaré en « protected » ce qui nous
permet de se passer des accesseurs
Cours Architectures logicielle 33 Cours Architectures logicielle 34
Exemple:
Exemple:
DecorateurIngredient va permettre de décorer les desserts
avec différents ingrédients. Il s’agit d’une classe abstraite
public static void main(String[] args)
{
héritant de dessert. Celle-ci possède en attribut le dessert
Dessert d1 = new Gaufre(); qu’elle va décorer (c’est à dire ajouter des fonctionnalités). A
d1 = new Chocolat(d1); noter, que ce dessert peut correspondre à un dessert déjà
System.out.println(d1); décoré puisque celui-ci hérite indirectement de la classe
Dessert. Le DecorateurIngredient oblige également la
Dessert d2 = new Crepe();
d2 = new Chocolat(d2);
redéfinition des deux méthodes getLibelle() et getPrix() dans
d2 = new Chantilly(d2); ses sous classes
System.out.println(d2);
} Cours Architectures logicielle 35 Cours Architectures logicielle 36
9
12/03/2017
Exemple: Rôle:
La classe principale de notre application fabrique deux On trouve des classes possédant des attributs dont les
desserts. Une gaufre au chocolat et une crêpe au chocolat
et à la chantilly. On affiche alors le libellé de ces desserts valeurs changent régulièrement. De plus, un certain
et leurs prix. On aura comme résultat nombre de classes doit être tenu informé de l’évolution
de ces valeurs. Il n’est pas rare d’être confronté à ce
problème notamment en développant une classe métier
Gaufre, chocolat : 2,000TND
et les classes d’affichages correspondantes.
Crêpe, chocolat, chantilly : 2,200TND
Rôle:
Rôle: On peut alors se demander quelle démarche adopter pour
Afin d’illustrer ce problème récurent, prenons un exemple que la classe chargée de l’affichage soit tenue informée en
volontairement simpliste. On considère une classe temps réel de l’heure courante stockée dans la classe
HeurePerso possédant dans le même attribut l’heure et la HeurePerso ?
minute courante. Cette classe, et plus particulièrement son
On peut identifier deux solutions.
attribut, est utilisé pour l’affichage de l’heure courante dans
Soit la classe d’affichage se charge de demander à la classe
une fenêtre, lors de l’écriture de logs... Pour cela, on définit HeurePerso la valeur de son attribut
une classe AfficheHeure qui se charge d’afficher l’heure et la soit c’est la classe HeurePerso qui informe la classe
minute courante dans une partie de la fenêtre. AfficheHeure lors de changements.
Cours Architectures logicielle 39 Cours Architectures logicielle 40
10
12/03/2017
Rôle: Rôle:
Il est facile de s’apercevoir que la première solution n’est La solution consiste donc à laisser la charge à la classe
HeurePerso d’informer sa classe d’affichage de ses
pas la meilleure. En effet, quand la classe AfficheHeure changements de valeurs. Cependant la classe
devra t-elle questionner HeurePerso pour obtenir l’heure HeurePerso doit pouvoir informer plusieurs classes
courante ? Toutes les minutes ? Toutes les secondes ? d’affichage et cela en évitant de lier fortement les classes
Quelque soit l’intervalle choisi, soit l’heure ne sera pas entre elles. C’est à dire qu’une modification des classes
précise soit on surchargera d’appels inutiles la classe d’affichage ne doit pas engendrer de modification dans
HeurePerso. la classe métier et vice versa.
Cours Architectures logicielle 41 Cours Architectures logicielle 42
Rôle: Rôle:
Le diagramme UML du pattern Observateur définit deux
interfaces et deux classes. L’interface Observateur sera
implémenté par toutes classes qui souhaitent avoir le
rôle d’observateur. C’est le cas de la classe
ObservateurConcret qui implémente la méthode
actualiser(Observable). Cette méthode sera appelée
automatiquement lors d’un changement d’état de la
classe observée.
Cours Architectures logicielle 43 Cours Architectures logicielle 44
11
12/03/2017
Rôle: Rôle:
On trouve également une interface Observable qui devra Le tableau d’observateurs correspond à la liste des
être implémentée par les classes désireuses de posséder
des observateurs. La classe ObservableConcret observateurs qui sont à l’écoute. En effet, il ne suffit pas à
implémente cette interface, ce qui lui permet de tenir une classe d’implémenter l’interface Observateur pour
informer ses observateurs. être à l’écoute, il faut qu’elle s’abonne à un Observable
Celle-ci possède en attribut un état (ou plusieurs) et un via la méthode ajouterObservateur(Observateur).
tableau d’observateurs. L’état est un attribut dont les
observateurs désirent suivre l’évolution de ses valeurs
Cours Architectures logicielle 45 Cours Architectures logicielle 46
Rôle: Rôle:
En effet, la classe ObservableConcret dispose de quatre ajouterObservateur(Observateur) et
supprimerObservateur(Observateur)
méthodes que sont :
permettent, respectivement, d’ajouter des observateurs
ajouterObservateur(Observateur) à l’écoute de la classe et d’en supprimer. En effet, le
supprimerObservateur(Observateur) pattern Observateur permet de lier dynamiquement
notifierObservateurs() (faire une liaison lors de l’exécution du programme par
opposition à lier statiquement à la compilation) des
getEtat(). observables à des observateurs.
Cours Architectures logicielle 47 Cours Architectures logicielle 48
12
12/03/2017
Rôle: Rôle:
notifierObservateurs() est appelée lorsque l’état subit un Une des questions récurrente face à ce pattern est pourquoi
changement de valeur. Celle-ci avertit tous les ces deux interfaces ? D’ailleurs on trouve sur Internet des
implémentations de ce pattern sans ces deux interfaces...
observateurs de cette mise à jour. La méthode getEtat() Mais l’utilisation de ces interfaces permet de coupler
est un simple accesseur en lecture pour l’état. En effet, faiblement l’observable à ses observateurs. En effet, un
les observateurs récupèrent via la méthode principe de conception est de lier des interfaces plutôt que
actualiser(Observable) un pointeur vers l’objet observé. des classes afin de pouvoir faire évoluer le modèle facilement.
Puis, grâce à ce pointeur, et à la méthode getEtat() il est L’utilisation de ces deux interfaces n’est donc pas obligatoire
possible d’obtenir la valeur de l’état. mais elle est vivement conseillée.
Rôle:
Rôle:
Cependant, il existe une variation possible lors de l’utilisation
de ce pattern. Dans la solution présentée ci dessous, une Dans ce cas, on passe directement l’état actuel de
référence vers l’objet observable est mis à disposition de l’observable dans la méthode actualiser(TypeEtat). Ainsi
chaque observateur. Ainsi les observateurs peuvent l’utiliser les observateurs disposent directement de l’état. Mais
pour appeler la méthode getEtat() et ainsi obtenir l’état de pourquoi avoir présenté la solution nommée « TIRER »
l’observable. Cette solution est nommée « TIRER » car c’est
aux observateurs, une fois avertis de l’évolution, d’aller plutôt que l’autre ? Parce qu’elle permet une fois de plus
chercher l’information sur l’état. Mais il existe la solution de lier faiblement l’observable à ses observateurs.
inverse appelée « POUSSER ».
13
12/03/2017
Rôle: Rôle:
En effet, si l’observateur dispose d’un pointeur vers Le pattern observateur permet de lier de façon
dynamique un observable à des observateurs. Cette
l’objet observable et que la classe observable évolue en solution est faiblement couplée ce qui lui permet
ajoutant un deuxième état. L’observateur souhaitant se d’évoluer facilement avec le modèle. D’ailleurs le pattern
tenir informé de ce deuxième état aura juste à appeler Observateur est très utilisé. Il fait partie, par exemple,
l’accesseur correspondant. Alors que si on « POUSSER » des patterns indispensables pour mettre en place le
il faudrait changer la signature de la méthode ce qui peut modèle MVC (Modèle Vue Contrôleur) très en vogue
s’avérer plus dommageable. actuellement ( pour de bonnes raisons)
Cours Architectures logicielle 53 Cours Architectures logicielle 54
Exemple: Exemple:
Le principe du Global Positioning System est simple. Il renouvelle l’opération avec trois autres satellites et
peut donc en déduire sa position dans l’espace (procédé
Une personne souhaitant connaître sa position utilise un appelé là trilatération).
récepteur GPS. Ce récepteur reçoit des informations
Comme dans la définition du pattern Observateur, on
(position, date précise…) d’au moins quatre satellites trouve également deux interfaces Observateur et
(sur un total de 24 satellites). Grâce à la date transmise, Observable. Pour résumer la classe Gps sera observable
le récepteur peut calculer la distance le séparant du et les classes AfficheResume et AfficheComplet seront
satellite dont il connaît la position. ses observateurs.
Cours Architectures logicielle 55 Cours Architectures logicielle 56
14
12/03/2017
Exemple: Exemple:
Considérons que notre ordinateur est relié à un
récepteur GPS par un réseau sans fil. On va concevoir
une classe nommée Gps qui va stocker les informations
du récepteur (positionnement, précision…). Puis deux
autres classes (AfficheResume et AfficheComplet)
permettant d’afficher de deux façons différentes ces
informations.
Cours Architectures logicielle 57 Cours Architectures logicielle 58
Exemple: Exemple:
15
12/03/2017
Exemple: Exemple:
L’interface Observateur impose aux observateurs public class Gps implements Observable
{
d’implémenter la méthode actualiser(Observable). private String position;
Celle-ci sera appelée automatiquement lors d’un private int precision;
private ArrayList tabObservateur;
changement d’état dans la classe observable. L’interface
Observable oblige les classes souhaitant disposer public Gps()
{
d’observateurs d’implémenter trois méthodes. position="inconnue";
precision=0;
tabObservateur=new ArrayList();
Cours Architectures logicielle 61 } Cours Architectures logicielle 62
Exemple: Exemple:
public void ajouterObservateur(Observateur o) public void setMesures(String position, int precision)
{tabObservateur.add(o); } { this.position=position;
this.precision=precision;
public void supprimerObservateur(Observateur o) notifierObservateurs(); }
{tabObservateur.remove(o);}
public String getPosition()
public void notifierObservateurs() {return position;}
{ for(int i=0;i<tabObservateur.size();i++){
Observateur o = tabObservateur.get(i); public int getPrecision()
o.actualiser(this); } {return precision;}
} }
Cours Architectures logicielle 63 Cours Architectures logicielle 64
16
12/03/2017
Exemple: Exemple:
La classe Gps implémente l’interface Observable et peut Un constructeur permet d’initialiser les attributs. Les
ainsi avoir des observateurs à son écoute. La liste des trois méthodes définies dans l’interface Observable sont
observateurs à l’écoute est stockée dans un tableau implémentées. De plus, on trouve deux accesseur en
dynamique en attribut. De plus, cette classe possède lecture correspondant aux deux états de la classe. En
deux états un correspondant au positionnement et effet, nous utiliserons la méthode « TIRER » comme
l’autre à la précision. La précision indiquera la fiabilité présentée dans la description du pattern Observateur.
du positionnement (plus on utilise de satellites plus la Enfin la méthode setMesures(String, int) permettra de
précision est forte). simuler la réception de valeurs par le récepteur GPS
Cours Architectures logicielle 65 Cours Architectures logicielle 66
Exemple: Exemple:
public class AfficheComplet implements Observateur
Public class AfficheResume implements Observateur
{
{
public void actualiser(Observable o)
public void actualiser(Observable o)
{
{
if(o instanceof Gps)
if(o instanceof Gps)
{ Gps g = (Gps) o;
{ Gps g = (Gps) o;
System.out.println("Position :" +
System.out.println("Position :"
g.getPosition() + " Précision : "
+g.getPosition());
+ g.getPrecision()+"/10");
}
}
}
}
}
Cours Architectures logicielle 67 } Cours Architectures logicielle 68
17
12/03/2017
Exemple:
Exemple:
Ces deux classes implémentent la méthode
actualiser(Observable) qui pour rappel est appelée public class Main
automatiquement lors de changement d’états de la classe { public static void main(String[] args)
{
observable. Dans cette méthode on vérifie que l’observable Gps g = new Gps();
est bien de type Gps et on utilise les accesseurs pour aller AfficheResume ar = new AfficheResume();
chercher le ou les états. La seule différence entre les deux AfficheComplet ac = new AfficheComplet();
classes concerne l’affichage en console de seulement la g.ajouterObservateur(ar);
position pour AfficheResume et d’avantage d’informations g.setMesures("N 39°59°993 / W 123°00°000", 4);
g.ajouterObservateur(ac);
(position et précision) pour AfficheComple g.setMesures("N 37°48°898 / W 124°12°011", 5);
}
Cours Architectures logicielle 69 Cours Architectures logicielle 70
}
Exemple: Exemple:
Comme dans les autres exemples d’implémentations de
patterns la classe Main correspond à la classe principale Résultat en console :
de notre programme. On commence par créer les objets
Position : N 39°59°993 / W 123°00°000
suivants : Gps, AfficheResume et AfficheComplet. Puis Position : N 37°48°898 / W 124°12°011
on abonne les observateurs à l’objet de type Gps et on Position : N 37°48°898 / W 124°12°011 Précision : 5/10
fait évoluer régulièrement les mesures.
18
12/03/2017
Exemple:
Une fois l’observateur AfficheResume abonné à
l’observable Gps il est tenu au courant des changements
d’état. Ce n’est pas le cas de l’objet de type
AfficheComplet car même s’il implémente l’interface
Observateur l’objet n’est pas ajouté à la liste des
observateurs de Gps. Une fois cette opération effectuée
on constate que les deux observateurs sont bien tenus
informés des changements d’état de Gps.
Cours Architectures logicielle 73
19