Documente Academic
Documente Profesional
Documente Cultură
avance : la
conception avant tout
(design patterns
l'emploi)
Par Michal Haberzettel (devil may cry)
www.openclassrooms.com
2/68
Sommaire
Sommaire ...........................................................................................................................................
Lire aussi ............................................................................................................................................
Programmation objet avance : la conception avant tout (design patterns l'emploi) .......................
Partie 1 : Le paradigme objet ..............................................................................................................
2
1
3
4
10
11
17
19
20
22
24
www.openclassrooms.com
Sommaire
3/68
Par
www.openclassrooms.com
4/68
Introduction ce cours
En guise d'introduction sur ce cours, je vais tenter de vous sensibiliser sur les problmes rcurrents lorsqu'on aborde la partie
programmation. Il est important de bien comprendre que programmer de manire objet est une tche qui n'est pas simple, nombre
de personnes qui se targuent de coder en objet n'appliquent pas correctement sa philosophie (on parle du paradigme objet).
Comme ce cours se veut pour des zros, je vais survoler des principes la gestion de projets complexes qui peuvent s'appliquer
des projets de trs petites envergures.
Pour aborder ce cours, j'estime qu'il est ncessaire d'avoir un minimum de pratique en programmation en s'tant dj "cass les
dents" sur diverses difficults. Vous pourrez cependant trouver la pense adopter pour concevoir en orient objet. Ces notions
peuvent se rvler trs intressantes surtout pour les nouveaux qui ont une vision issue des langages impratifs.
J'insiste sur ce point car on aperoit que la plupart des problmes des codes dvelopps en objet viennent de complications
souleves en amont que l'on a voulus reporter l'tape de codage. Il ne faut pas se leurrer, nous pratiquons ce report tous plus
ou moins grande chelle afin d'arriver le plus rapidement la programmation. Cependant, c'est une mauvaise manire de faire car
tablir un programme se base sur le cycle (simplifi) suivant :
Notez que cette gestion de projet s'inspire du cycle incrmental (mais simplifi). C'est la gestion la plus intuitive que l'on
mne dans un projet. C'est dire qu'on s'attaque des parties spcifiques (un module, une fonctionnalit) que l'on va
rsoudre et dvelopper avant de passer un autre problme (la flche qui repart du dveloppement vers l'analyse).
Raliser le projet de la sorte permet de tester son programme au fur et mesure de son avancement et de valider ou
changer la partie concerne. Mme si d'autres cycles de gestion existent (spirale, en V, mthodes agiles, ...), tous
contiennent au moins les phases prsentes dans ce tutoriel. Mon discours reste valable dans les grandes lignes pour
ces modles.
www.openclassrooms.com
5/68
Ce modle emprunt au cycle de gestion d'un projet de grande envergure est intressant sur plusieurs points mais comme il
faudrait un cours complet pour expliquer ses principes, nous nous attarderons seulement sur les grandes phases. Ces phases se
suivent dans un ordre bien prcis qu'il est pratiquement impossible de faire autrement mme lorsque vous crivez le plus petit
programme qu'il soit (sauf si vous tes un prestataire avec un cahier des charges, ce qui doit tre le cas de 0% des zros
)!
Attends une seconde, comment a on respecte ce cycle ? Puis d'abord j'y comprends rien ton schma !
Pas trop vite ! Dcrivons ces phases sommairement pour savoir quoi elles correspondent rellement :
Analyse : C'est la phase qui s'occupe de poser les problmatiques gnrales au projet et d'y rpondre. Ici, on n'aborde
pas l'aspect programmation.
Conception : Une phase tout aussi importante, elle prcde la programmation relle en s'attardant sur "comment
modliser" tel problme dans mon code.
Dveloppement : C'est le code produit en fonction des deux premiers points, c'est donc une tape qui doit tre considre
avec autant de poids que les autres.
Maintenance & volutions : Un point qui peut paratre secondaire mais il n'en est rien. C'est ce dernier qui le plus
consommateur en temps. Les raisons en seront voques juste aprs.
Si vous ne voyez pas le rapprochement avec le code que vous vous amusez crire, prenons un cas concret la mode sur le site
du zro : le RPG et dcortiquons. Comment va se structurer votre pense si vous vous fixez comme objectif de coder un RPG
simple en mode console ? Logiquement vous allez vous poser les questions suivantes :
Que va devoir grer mon RPG (personnages, armes, magies, ...) ? Quelles sont les limites que je m'impose ?
Avec cette "analyse", vous arrivez entrevoir les classes correspondantes et leurs interactions avec d'autres classes.
Vous lancez votre diteur de code favori et vous commencez dvelopper en vous basant sur votre pense prcdente.
Une fois que vous avez russi programmer une partie spcifique (exemple : le personnage avec toutes ses interactions),
vous vous attardez sur un lment apprhender.
Vous jonglez constamment sur les 3 points prcdents lorsque vous crivez du nouveau code (ce qui s'apparente aux
volutions dans notre cas).
Intrinsquement vous respectez donc (presque) toujours ce cycle mme si c'est de manire implicite. Vous vous demandez encore
srement pourquoi je vous ai balanc ce schma. Bien, venons-en aux faits : Que se passe-t-il lorsque vous programmez et que
vous tombez sur un problme. Le problme du genre (en reprenant l'exemple d'un RPG) :
"J'ai un magicien qui hrite de personnage et un barbare qui hrite de personnage. Je propose dans mon jeu de pouvoir crer un
personnage avec 2 catgories, il faut donc crer un magicien barbare qui hrite de magicien et de barbare. J'ai maintenant une
classe voleur (une nouvelle fonctionnalit frachement rajoute dans le jeu), je vais maintenant crer les couples magicien voleur
et voleur barbare."
Le problme se profile trs clairement, que va t'il se passer si je dcide d'ajouter un nouveau type de personnage comme par
exemple assassin ? Il faudra crer les classes assassin voleur, magicien assassin, barbare assassin. C'est une solution peu flexible
et qui augmente le nombre de classes trs rapidement. Cette erreur va vite devenir un cauchemar, jusqu' ce que vous dcidiez de
changer votre manire de faire en vitant de reproduire l'erreur. Vous refaites alors de la conception pour corriger cette lacune.
Voyons quels sont les impacts ce mode de fonctionnement sur mon schma :
Les flches rouges reprsentent les rpercussions que peuvent entraner une erreur selon la phase. Par exemple, une erreur
www.openclassrooms.com
6/68
l'tape du dveloppement ncessite de revoir la conception. Revoir la conception peut impliquer de revoir aussi l'analyse.
Si vous devez retenir une chose de tout ce qui a t dit cela se formulerait par :
Il est important de prendre du temps sur l'analyse et surtout (pour le cas des zros qui programment par plaisir) de
consacrer des ressources sur la conception !
De trs nombreux maux sur les forums de ce site sont dus une mauvaise conception. Apprenez bien concevoir et vous vous
rendrez trs vite compte des effets bnfiques que vous pourrez en tirer. La bonne conception vient aussi en pratiquant,
planchez vous sur des problmes en essayant diffrents angles d'approches et mesurer les avantages/inconvnients sur
chacune pour en tirer un maximum de bnfice.
Avant que celui du fond ne sorte la cagette de tomates, prenez le temps de lire ce qui suit. Je ne vous le cache pas, c'est un
investissement que de passer apprendre ces patrons de conception, on peut cependant en tirer plusieurs bnfices notables. Si
vous trouvez une partie de votre code qui exige un design pattern voil les gains apports :
www.openclassrooms.com
7/68
Une documentation facilement accessible sur le design pattern utilis. Si vous mme ou quelqu'un d'autre ayant
connaissance de ce qu'est un design pattern, il n'y aura pas besoin de commenter de manire complexe le code. Au pire, si
la personne qui tombe sur votre code ne connat pas les patrons de conception, il n'aura que l'apprentissage de celui
concern. C'est un gain de temps notable de tous les cts et a vite les prises de ttes (pour vous y compris).
Votre code (si vous avez correctement implment le patron) est ouvert aux apports et sans contrepartie pour la solution
que vous avez employe au problme pos. Vous pourrez donc trs facilement faire voluer le code sans risquer de devoir
tout modifier.
En conclusion de cette partie, si vous devez retenir une chose c'est que les patrons de conceptions ne sont pas indispensables
pour bien concevoir, mais pour une problmatique donne, ils y rpondent d'une manire lgante ( leur poque).
videmment, ce ne sont pas les seules recherches qui ont fait progresser l'orient objet, de trs grands auteurs comme
James O. Coplien ont offert d'autres approches pour concevoir efficacement. Vous verrez un tour d'horizon des
principales ides dans le prochain chapitre des influences portes par ces experts.
www.openclassrooms.com
8/68
Diagramme de cas d'utilisations d'une pizzeria. Ne vous attardez pas dessus vous ne connaissez pas le contexte.
Rappelons juste que ce diagramme permet de reprsenter les fonctionnalits auxquelles l'application doit rpondre.
Notez que grce ce schma on peut dj connatre beaucoup de choses sur l'application. Il est possible d'entrevoir un
dcoupage, des traitements et les diffrentes interfaces hommes machines que l'on devra crer selon le profil utilisateur. Je ne
vais pas expliquer comment modliser ce diagramme mais il ne faut pas oublier qu'il est toujours accompagn d'un descriptif.
Nous sommes plus dous pour se reprsenter les choses par des dessins que par du simple texte, ne sous-estimez donc pas
l'importance de passer par cette modlisation visuelle pralable.
D'autres diagrammes tels que le diagramme de squence, le diagramme d'activit ou encore le diagramme d'tats-transitions
offrent par la mme occasion une aide considrable pour raliser une conception qui reflte au mieux ce que vous avez cern. Le
plus intressant lorsqu'on modlise, c'est que sur certains points, on commence buter et ce sont gnralement ces parties-l qui
vont vous causer du tort plus tard. Sauf qu'ici, l'impact de ces soucis a des consquences moindres que si on s'tait dj lanc
dans le dveloppement. Si vous dmarrez de nouveaux projets qui commence avoir de l'ampleur, il est vivement conseill de
faire une modlisation pralable et encore plus si vous travaillez en quipe.
Toutes ces tapes qui prcdent le dveloppement peuvent vous sembler superflues mais c'est tout le contraire. A
www.openclassrooms.com
9/68
dfaut de perdre du temps l'laboration de l'analyse, vous gagnerez un temps considrable par la suite car la majorit
des problmes auront t anticips. Toute forme quelconque de texte ou de schma sert aussi pour crer une
documentation solide, dans une autre mesure ces lments
peuvent tre des constituants d'un cahier des charges.
Le second point pour concevoir efficacement est de connatre ce qui se cache derrire le nom de programmation oriente objet,
sa raison d'tre et comment elle devrait tre utilise. Ce que l'on nomme plus scientifiquement "le paradigme objet". C'est ce sur
quoi le prochain chapitre va porter.
J'espre que cette petite introduction vous aura mis l'eau la bouche. Si vous tes motiv pour apprendre ce qui suit ou
simplement curieux, continuez la lecture, vous ne serez pas dus. Cependant, avant de se lancer dans lexplication de certains
patrons de conception, il me parat indispensable de vous poser les bases de l'objet. Je n'ai malheureusement pas vu sur le site
du zro un cours s'y rapprochant, je vais donc faire un rapide apart afin de mieux comprendre comment les patrons de
conceptions ont t conus.
Je pense que vous aborderez la vision de l'objet sous un autre angle la fin du chapitre suivant si certains des principes
voqus vous taient mconnus. Si vous tes toujours l, allons-y !
www.openclassrooms.com
10/68
Venons-en maintenant aux faits, si vous tes toujours l et que les parties suivantes seront nouvelles pour vous, j'ose dire que
vous ne verrez plus l'orient objet de la mme faon. C'est parti !
Les codes que je vous prsenterai sont donns titre d'exemple et pas forcement compilables. Ne soyez donc pas tonns si
vous trouvez que certaines portions sont manquantes, je mintresse uniquement la partie utile du code. Cette remarque est
d'autant plus vrai pour les diffrents exemples donns en code Java afin d'viter l'alourdissement du chapitre.
Offrez du service
Le point que je considre comme l'un des plus importants en programmation oriente objet est probablement sur les oprations
que proposeront vos classes. Lorsque vous concevez une nouvelle classe qui s'avre complexe dans ses traitements, la premire
chose que nombre d'entre vous faites est de crer des getters / setters sur vos variables afin de pouvoir y accder en dehors de
la classe. Ce n'est pas obligatoirement une mauvaise chose mais dans la plupart des cas c'est proscrire.
Pour construire efficacement les mthodes d'accs publiques, il faut penser diffremment; Adopter une approche en termes de
services rendus.
Mais qu'appelles-tu un service ?
C'est simple, prenons un exemple. Je viens de crer ma classe, il faut simplement imaginer qu'est-ce que peut proposer ma classe
aux autres ayant un intrt. Ou avec du code :
Code : C++
MaClasse instance;
instance. // Les mthodes utiles aux utilisateurs de cet objet.
Code : Java
MaClasse instance = new MaClasse();
instance. // Les mthodes utiles aux utilisateurs de cet objet.
Pour tablir des services efficaces, il faut se positionner en tant qu'utilisateur extrieur de la classe et dfinir les mthodes qui lui
seront utiles.
C'est donc de construire sa classe depuis lextrieur sans rflchir ce qu'elle contient. En prenant un exemple extrme, ma classe
peut contenir 100 variables prives utiles son bon fonctionnement (pour des raisons de performance par exemple), si une seule
mthode est utile pour les utilisateurs de cette classe, alors pas besoin d'offrir des mthodes d'accs inutiles.
www.openclassrooms.com
11/68
Si vous n'arrivez pas raliser efficacement une construction de mthodes avec des services, vous pouvez simplement procder
de la manire suivante :
Code : C++
class NouvelleClasse
{
// Avant n'importe quel code, on va
mthodes qui nous semblent indispensables.
// pour les utilisateurs extrieurs
};
construire
toutes
les
Ce n'est pas plus compliqu et vous amliorerez grandement la comprhension de votre code que si vous ne rflchissiez pas en
termes de service. Essayez juste de raisonner simplement pour offrir des services prcis et concis.
Pour faire un rapprochement avec la ralit, vous pouvez prendre un four lectrique :
Soit le code :
www.openclassrooms.com
12/68
Code : C++
class Personne
{
public :
const std::string& getNom() const { return m_nom }
void setNom(const std::string &nouveauNom) { m_nom = nouveauNom;
}
};
private :
std::string m_nom;
Code : Java
class Personne
{
public String getNom() { return nom; }
public void setNom(String nouveauNom) { nom = nouveauNom; }
}
Le seul cas o il pourrait tre judicieux d'crire du tel code, c'est lorsque vous suspectez une possible volution des contrles
qui pourront tre appliqus sur le nom. Par exemple, que la mthode setNom vrifie que le nom pass en paramtre ne soit pas
vide. Quand le doute est permis, n'hsitez pas encapsuler la proprit, cela pourrait viter une refactorisation importante du
code. Cependant, ne vous leurrez pas, si vous faites un couple getter/setter comme au-dessus sans tenir compte de la rgle (c'est
le cas des conventions Java par exemple
), vous offrez un accs direct votre variable membre, ce qui la rend indirectement
publique.
Voyons ce que peut donner l'utilisation de ces bonnes pratiques :
Code : C++
// Le code suivant va utiliser le mot clef struct en rfrence
une structure en c++.
// Cela quivaut une classe hormis que les attributs et mthodes
sont publics par dfaut.
/** Dfinition d'un point avec une position en X et Y en tant que
structure.
* Pourquoi sembter placer des getters/setters inutiles qui
n'apporteront jamais
* de valeur ajoute aux attributs publics.
*/
struct Point
{
Point(int ptX, int ptY) { x = ptX; y = ptY; }
int x, y;
};
/** Classe permettant de reprsenter une couleur
composantes Rouge, Vert, Bleu
* et le canal alpha (la transparence)
*/
class Couleur
{
public : // Tout ce qui suit est en accs publique
selon
Couleur (int rouge, int vert, int bleu, int alpha = 255) :
m_r(corrigerIntervalleComposante(rouge)),
m_v(corrigerIntervalleComposante(vert)),
m_b(corrigerIntervalleComposante(bleu)),
m_alpha(corrigerIntervalleComposante(alpha))
{
www.openclassrooms.com
les
13/68
}
// Comme il y a un contrle sur les composantes, l'usage de
getters/setters semble justifi
int getRouge() const { return m_r; }
int getVert() const { return m_v; }
int getBleu() const { return m_b; }
int getAlpha() const { return m_alpha; }
void setRouge(int valeur) { m_r =
(corrigerIntervalleComposante(valeur)); }
void setVert(int valeur) { m_v =
(corrigerIntervalleComposante(valeur)); }
void setBleu(int valeur) { m_b =
(corrigerIntervalleComposante(valeur)); }
void setAlpha(int valeur) { m_alpha =
(corrigerIntervalleComposante(valeur)); }
private : // Tout ce qui suit est rserv la classe ou ses
drives
int corrigerIntervalleComposante(int valeurComposante)
{
if(valeurComposante > 255) // Une constante aurait t plus
judicieuse mais inutile pour l'exemple
return 255;
else if(valeurComposante < 0)
return 0;
return valeurComposante;
}
int m_r, m_v, m_b, m_alpha;
};
class Image
{
public :
static bool charger(string nomFichier);
const Point& getTailleImage() const; //
C++, le mot clef const trs important
Pour
les
gens
du
monde
Code : Java
/** Dfinition d'un point avec une position en X et Y en tant que
structure.
* Pourquoi sembter placer des getters/setters inutiles qui
n'apporteront jamais
* de valeur ajoute aux attributs publics.
www.openclassrooms.com
14/68
*/
class Point
{
public Point(int ptX, int ptY) { x = ptX; y = ptY; }
public int x, y;
}
/** Classe permettant de reprsenter une couleur selon
composantes Rouge, Vert, Bleu
* et le canal alpha (la transparence)
*/
class Couleur
{
public Couleur (int rouge, int vert, int bleu, int alpha)
{
m_r = corrigerIntervalleComposante(rouge);
m_v = corrigerIntervalleComposante(vert);
m_b = corrigerIntervalleComposante(bleu);
m_alpha = corrigerIntervalleComposante(alpha);
}
les
www.openclassrooms.com
15/68
Ce code est dj un bon exemple des services rendus, si vos classes proposent les bons services, les classes peuvent voir leur
implmentation interne compltement change sans aucune incidence sur le code. Imaginons maintenant que l'application est un
logiciel de retouche d'images, il est crucial d'optimiser la place mmoire, on peut amliorer les classes existantes sans impacter le
service offert, voyez plutt :
Code : Java
// Utilisation des oprateurs de bits. L'utilisateur de la classe
n'en a que faire !
// Cependant, une couleur prend 4 fois moins d'espace mmoire
qu'avant. il y a cependant un traitement faire.
class Couleur
{
public Couleur (int rouge, int vert, int bleu, int alpha)
{
m_stockageBinaire = 0;
m_stockageBinaire |= corrigerIntervalleComposante(rouge) <<
24;
m_stockageBinaire |= corrigerIntervalleComposante(vert)<< 16;
m_stockageBinaire |= corrigerIntervalleComposante(bleu)<< 8;
m_stockageBinaire |= corrigerIntervalleComposante(alpha);
}
public Couleur (int rouge, int vert, int bleu)
{
this(rouge, vert, bleu, 255);
}
// Comme il y a un contrle
getters/setters semble justifi
public int getRouge() { return
public int getVert() { return
public int getBleu() { return
public int getAlpha() { return
>>> 24; }
<< 8 >>> 24; }
<< 16 >>> 24; }
<< 24 >>> 24; }
-=
-=
-=
&=
www.openclassrooms.com
16/68
Pour la petite anecdote, la classe Couleur aurait pu tre optimise avec le code suivant (mais disponible que sur certains
langages comme le C++ qui possde des types non signs).
Code : C++
// En s'assurant que le char soit cod en 8 bits (256 valeurs) :
struct Couleur
{
Couleur (unsigned char rouge, unsigned char vert,
unsigned char bleu, unsigned char canalAlpha = 255)
{
r = rouge;
v = vert;
b = bleu;
alpha = canalAlpha;
}
};
Je rappelle que ce tutoriel n'a pas la prtention d'optimiser chaque ligne de code, surtout que ces diffrences sont issues des
particularits de langages gnralement.
En conception pure, la premire classe Couleur code est valide, n'oubliez pas que le langage vient normalement se greffer aprs
et c'est l que viennent les optimisations. Noter aussi qu'au lieu de pondre ces solutions, il existe un design pattern qui permet de
rduire normment la place que peuvent prendre des petits objets identique (c'est le cas avec les couleurs d'une image). Ce
patron de conception s'appelle poids mouche ou flyweight et sera abord plus tard.
Il est utile de constater que les classes qui servent uniquement de structures de donnes brutes se passent trs
gnralement du couple getter/setter. En ce sens, le C++ offre le mot clef struct qui offre une meilleur smantique que le
Java par exemple.
Ouf, pour rsumer, voil comment on pourrait formuler la chose (je vais r-insister pour tre sr qu'il n'y ai pas de malentendu) :
Lorsqu'on construit sa classe, on va proposer des services.
Certains de ces services correspondent l'accs en lecture des proprits de la classe.
On regarde ensuite s'il y a des proprits qui doivent tre en criture dans les services proposs.
Dans le cas de structures de donnes, la variable membre devrait tre publique sinon on crit l'accesseur et/ou mutateur
correspondant.
Si vous n'tes pas convaincu que les accesseurs/mutateurs ne sont pas toujours une bonne solution, beaucoup de sujets
traitent de ce problme sur internet. Une simple recherche avec l'expression "why getters setters are evil" vous donnera une
foule d'arguments qui se tiennent. Voil un exemple qui pourra vous faire changer d'avis sur l'utilisation non rflchie des setters
:
Code : C++
class Compteur
{
public :
Compteur(unsigned int tickDepart = 0);
unsigned int getTick() const ;
void setTick(unsigned int nouvelleValeur);
void reinitialiser();
};
private :
unsigned int m_cpt;
www.openclassrooms.com
17/68
Code : Java
class Compteur
{
public Compteur();
public Compteur(int tickDepart);
public int getTick();
public void setTick(int nouvelleCaleur);
public void reinitialiser();
}
Ce code prsente deux inconvnients majeurs. Tout d'abord, il rvle en quelque sorte la structure interne de la classe Compteur
car on peut y dceler les mthodes get/set correspondantes, rappelez-vous, l'utilisateur n'a pas faire de suspicion sur les
rouages internes de la classe. Le second problme plus grave est que le setter offre une trop grande libert l'utilisateur, un
compteur ne doit pas offrir la possibilit de changer sa valeur lorsqu'il... compte ! Une implmentation largement plus claire qui
corrigerait ces deux dfauts pourrait tre :
Code : C++
class Compteur
{
public :
// Rajout d'une variable pas pour plus de souplesse
Compteur(unsigned int tickDepart = 0, unsigned int pas = 0);
unsigned int tickActuel() const ;
unsigned int incrementer(); // Incrmente du pas et retourne la
nouvelle valeur
void reinitialiser();
};
private :
unsigned int m_cpt, m_pas;
Code : Java
class Compteur
{
public Compteur();
public Compteur(int tickDepart);
public Compteur(int tickDepart, int pas); // Ajout d'un pas pour
plus de souplesse
public int tickActuel();
public int incremente(); // Incrmente du pas et retourne la
nouvelle valeur du compteur
public void reinitialiser();
}
Mme si c'est une classe triviale, il faut bien comprendre les enjeux pour des structures plus complexes. Le raisonnement n'est
pas si difficile avoir et le code gagne normment en auto-documentation. Vous m'accuserez peut tre de tricher en rajoutant un
pas pour le compteur; C'est vrai, mais je voulais surtout prendre un exemple qui mettait l'accent sur deux codes semblables dont
l'un n'est pas fondamentalement faux mais dont le second est beaucoup plus idiomatique (respect des bonnes pratiques).
Plus d'informations ce sujet :
- Blog d'Emmanuel Deloget : La guerre des accesseurs.
- Article sur javaworld.com : [Anglais] Why getter and setter methods are evil de Allen Holub .
Responsabilit limite
Cette remarque ne concerne pas que la conception objet. Dans tout systme, il est important de dcomposer les rles qu'auront
vos classes. Il faut surtout viter se retrouver avec une classe "utilitaire" qui s'occupe de beaucoup de ressources la fois et
www.openclassrooms.com
18/68
dont son rle est mal dfini. Prenons pour exemple une classe ConvertHTML avec le code suivant :
Code : C++
class ConvertHTML
{
public:
ConvertHTML(const std::string &source);
};
Code : Java
class ConvertHTML
{
public ConvertHTML(String source);
public
public
public
public
// ...
void
void
void
void
toPdfFile(String nomFichierDest);
toRtfFile(String nomFichierDest);
toTxtFile(String nomFichierDest);
toWordFile(String nomFichierDest);
Cet exemple montre vite que trop de choses sont gres au sein de cette mme classe, on va vite arriver des complexits dans le
code qui vont impliques des modifications successives. C'est surtout le cas pour le format Pdf ou le format Word qui permettent
des structures d'affichages aussi complexes que le Html. L'idal est simplement de dporter du code afin d'avoir une architecture
de code plus flexible. Il suffit donc d'clater la classe en plusieurs plus petites qui se chargent de remplir un rle mieux dfini et
de manire indpendante. Grce cette division en sous classes vous favorisez aussi la rutilisation de ces portions de codes
qui sont moins dpendants d'un contexte prcis. Dans l'exemple prcdent, il faudrait que chaque mthode voit son traitement
dport dans des classes indpendantes comme PdfFromHTML.
Il faut faire preuve d'un peu de bon sens dans cet clatement des rles. Si vous voyez une utilit trs faible dcoupler
alors ne le faites pas. Essayez juste de voir si votre classe peut fonctionner dans un autre contexte que le votre.
Si vous avez tendance vouloir attribuer beaucoup de responsabilit une classe, essayez de dcouper le problme, vous
sentirez trs vite son importance. D'ailleurs, beaucoup de patrons de conceptions se servent de ce principe pour avoir des codes
trs modulables. Certes, le projet voit son nombre de classes augmenter mais sa maintenance en sera rduite.
Une chose importante que je n'ai pas spcialement mis en vidence : il faut viter que vos classes soient trop dpendantes les
unes par rapports aux autre. En rejoignant un peu le principe de responsabilit unique, plus vos classes peuvent se passer de
liens troits avec d'autres classes en relation, plus serez apte produire du code interchangeable ou rutilisable dans d'autres
contextes avec d'autres avantages. Essayez de favoriser un couplage faible est une clef de russite. La cohsion pour un
systme est aussi un atout majeur pour savoir si votre dcoupage en classes est efficace.
Mais c'est quoi la cohsion dans la conception ?
Il suffit tout simplement de prendre le nom de la classe, par exemple "ConvertHTML" et de savoir quels sont les services
proposs par celle-ci. Si le nom de la classe est suffisamment vocateur pour les oprations proposes, on suppose que la classe
joue un rle que l'on peut clairement dfinir. Plus la dfinition du rle est prcise, plus la cohsion sera forte.
Plus d'informations ce sujet :
- Le concept de base est appel SRP.
- L'autre concept prsent aprs porte le nom de couplage et cohsion (application avec les patrons GRASP en Java).
- Blog d'Emmanuel Deloget : le principe de responsabilit unique.
- Article MSDN : cohsion et couplage.
www.openclassrooms.com
19/68
Je vous conseille vivement de lire les deux articles prsents. Il sont trs enrichissants et accessible facilement au
niveau de la difficult.
Code : Java
//Mauvaise version, il n'y a pas d'hritage priv en Java
class Pile extends ArrayList
{ /* TOUTES LES METHODES D'ACCES DIRECT SERONT PROPOSEES + CELLES DE
LA PILE. */ }
// La solution base de composition.
class Pile<T>
{
private ArrayList<T> m_pile; // La composition consiste avoir
une variable du type souhait au lieu d'en hriter.
}
Il est tentant d'hriter pour viter de rcrire du code mais une chose essentielle a t oublie. L'hritage doit tre effectu
lorsque la classe fille "est une" classe mre avec des fonctionnalits en plus mais jamais moins. La classe Pile n'est pas une
classe Vector spcialise, elle adopte un comportement diffrent et un peu de bon sens suffit s'en rendre compte. Pour vous
aider, rares sont les cas o hriter publiquement (type hritage impos dans java) d'une classe concrte est prfrable une
composition.
www.openclassrooms.com
20/68
Cependant, ce n'est pas la seule chose qui est mise en avant par cette personne. Son domaine d'tude s'attardait sur la
programmation par contrat en orient objet et d'autres principes ont t voqus lors d'un hritage. Le lien wikipdia donn
pourra plus vous informer si le sujet vous intresse.
Plus d'informations ce sujet :
- Acronyme LSP pour la thorie de Liskov.
- Article sur developpez : Composition et hritage de Romain Guy.
- Article sur Wikipdia : Principe de substitution de Liskov.
- Inspiration du contenu : www.crossbowlabs.com (site ferm).
Pensez interface
Comme vous avez pu vous en rendre compte, les principes voqus prcdemment peuvent s'appliquer d'autre paradigmes que
celui de l'objet, on peut aussi bien placer ces arguments pour un langage impratif voir mme fonctionnel. Une interface quant
elle reprsente une faade qui propose des oprations. Plus gnralement, elle dcrit des comportements que l'on peut utiliser. En
programmation objet il est possible de reprsenter ce concept explicitement. Je vais faire un rappel mais si ce terme est nouveau
pour vous c'est peut tre que vous n'avez pas un niveau suffisant pour lire ce cours.
Les interfaces n'ont rien voir avec ce que l'on appelle IHM. Ce n'est en rien une reprsentation visuelle de quoi que ce
soit.
Une interface se dfinit comme suit :
Code : C++
/** En C++ une interface n'est rien d'autre qu'une classe qui ne
comporte que des mthodes
* publiques et virtuelles pures. Il n'y a aucune implmentation de
code (donc pas de variables membres)
*
* Cette interface va permettre de rcuprer la valeur selon le nom
d'un champ dans une source de
* donnes.
*/
class IRequete
{
public:
// Retourne faux si le champ n'a pas t trouv dans la source
de donnes.
// Si le champ est trouv, sa valeur sera crite dans le
second paramtre.
virtual bool requeter(const std::string &nomChamp, std::string
&valeurChamp) const = 0;
// Ces mthodes ne sont pas ncessairement constantes.
};
Code : Java
/** Java comme d'autres langages plus rcents possde un mot clef
www.openclassrooms.com
21/68
Dfinir une interface n'est pas bien compliqu, c'est une base pour induire des comportements des classes plus concrtes. Un
code possible serait :
Code : C++
class BDDRelationnelle : public IRequete// Comme un hritage
{
// On est maintenant oblig de redfinir les mthodes issues de
l'interface.
};
class XML : public IRequete// Comme un hritage
{
// On est maintenant oblig de redfinir les mthodes issues de
l'interface.
};
Code : Java
class BDDRelationnelle implements IRequete
{
// On est maintenant oblig de redfinir les mthodes issues de
l'interface.
}
class XML implements IRequete
{
// On est maintenant oblig de redfinir les mthodes issues de
l'interface.
}
Certains pourront se poser la question de la diffrence avec une classe abstraite. La rponse est simple, une classe abstraite
permet une factorisation de code des classes filles. L'interface va forcer implmenter des comportements (on peut l'assimiler
un contrat). On pourra ds lors passer par une interface pour effectuer des requtes alors qu'on manipule des objets concrets. Je
vais mettre un exemple pour illustrer plus facilement :
Code : C++
// On va utiliser le polymorphisme pour utiliser l'interface la
place de la classe concrte.
std::string valeurRetour;
bool succesRequete;
// Pour faire du polymorphisme, le C++ oblige utiliser les
pointeurs.
// On pourrait utiliser des pointeurs intelligents mais ce n'est pas
l'objet de ce cours.
IRequete *requeteur = new BDDRelationnelle();
www.openclassrooms.com
22/68
On
On
Code : Java
// On va utiliser le polymorphisme pour utiliser l'interface la
place de la classe concrte.
String valeurRetour;
IRequete requeteur = new BDDRelationnelle();
valeurRetour = requeteur.requeter("nomSARL"); // On requte sur la
base de donnes relationnelle
if(valeurRetour != null)
System.out.println(valeurRetour);
requeteur = new XML();
valeurRetour = requeteur.requeter("nomSARL"); // On requte sur la
base de donnes relationnelle
if(valeurRetour != null)
System.out.println(valeurRetour);
A travers cet exemple on voit qu'on lve le niveau d'abstraction du programme, il devient simple de rajouter une nouvelle source
de donnes (le CSV pour la forme) sans impacter le reste du code. Si je sais qu'il adoptera le comportement de mon interface,
alors je pourrais l'interroger de la mme manire, peu importe que son implmentation soit proche ou trs loigne de celle du
XML.
Les interfaces sont la base de nombreux design pattern car elles apportent normment de flexibilit en se dispensant de faire la
moindre supposition sur les objets qui l'implmenteront. On sait juste que ces derniers doivent se contenter de satisfaire ces
actions. L'ouvrage du GoF se permet mme de faire l'assertion suivante :
"Programmer pour une interface, non pour une implmentation"
Si vous programmez avec des bonnes interfaces votre code sera beaucoup plus robuste aux changements et vous pourrez
utiliser certaines fonctionnalits communes efficacement. L'exemple le plus parlant est sans doute l'itrateur Java pour parcourir
une collection. Pas besoin de connaitre la structure interne d'une collection parmi les nombreuses existantes pour naviguer sur
les lments qui la constitue, il suffit d'utiliser l'interface Iterator. Vous pouvez donc parcourir une liste chane, un tableau
dynamique, une structure en arbre ou je ne sais quoi de la mme manire. Avouez que c'est vraiment utile.
L'itrateur est issu d'un design pattern du mme nom "iterator" qui n'est pas si simple mettre en place. Ce que vous
pouvez constater si vous avez dj utilis ces objets (je l'espre pour vous), c'est qu'il est plutt "simple" de s'en servir
du point de vue utilisateur.
Retenez donc que les interfaces sont le dbut de la programmation flexible en objet. Grce elles, vous pouvez appliquer le
principe de substitution pour vous passer dlments concrets lors de la conception.
www.openclassrooms.com
23/68
dont la provenance est insouponne. C'est un fait, nous ne sommes pas parfaits, patcher un code qui tait fonctionnel peut
avoir des incidences car de nouveaux cas non anticips surviennent.
La solution ce problme est de prvoir que de nouvelles fonctionnalits donneront lieu des ajouts dans le code sans toucher
l'existant. Je ne vous le cache pas, c'est une tche ardue que de rendre sa conception suffisamment solide pour que les
nouveaux besoins viennent se greffer sur ce qui existe. Ce serait une sorte d'addon si vous voulez. Les patrons de conception
misent normment pour viter une modification de code si une fonctionnalit venait se produire. C'est peut tre l'un de leur
plus gros point fort, l'ajout d'un besoin rajoutera uniquement du code sans aucune forme daltration. C'est d'ailleurs pourquoi ils
sont si efficaces lorsqu'ils rpondent un problme : Le risque d'erreur d'implmentation est trs faible.
En reprenant l'exemple prcdant sur la classe ConvertHTML et son implmentation sans dcoupage des responsabilits voici ce
qu'on obtient :
Code : C++
class ConvertHTML
{
public:
ConvertHTML(const std::string &source);
void toPdfFile(const std::string &nomFichierDest) { /*
D'IMPLEMENTATION */ }
void toRtfFile(const std::string &nomFichierDest) { /*
D'IMPLEMENTATION */ }
void toTxtFile(const std::string &nomFichierDest) { /*
D'IMPLEMENTATION */ }
void toWordFile(const std::string &nomFichierDest) { /*
D'IMPLEMENTATION */ }
// ...
};
CODE
CODE
CODE
CODE
Code : Java
class ConvertHTML
{
public ConvertHTML(String source);
public void toPdfFile(String nomFichierDest) { /*
D'IMPLEMENTATION */ }
public void toRtfFile(String nomFichierDest) { /*
D'IMPLEMENTATION */ }
public void toTxtFile(String nomFichierDest) { /*
D'IMPLEMENTATION */ }
public void toWordFile(String nomFichierDest) { /*
D'IMPLEMENTATION */ }
// ...
}
CODE
CODE
CODE
CODE
Si on a un problme on va tre oblig d'intervenir sur cette classe. Une nouvelle conversion va aussi imposer de mettre son
implmentation dans ce code. Vous vous retrouvez donc avec une classe de milliers de lignes de code. Une structuration plus
intelligente utiliserait une interface capable d'abstraire le comportement des formats de destination. Quel est le comportement que
l'on cherche gnraliser lors de la conversion ? Tout simplement la transformation, on peut donc crer l'interface
correspondante :
Code : C++
// En C++
class IConvertisseurHTML
{
public :
virtual void fromHTMLToFile(const std::string &html, const
std::string & fichierDestination) const = 0; // virtuelle pure
};
www.openclassrooms.com
24/68
Code : Java
// En Java
interface IConvertisseurHTML
{
public void fromHTMLToFile(String html, String
fichierDestination);
}
Ensuite on peut imaginer que la classe qui transforme le Html en Pdf va implmenter cette interface. Cependant je ne m'tends
pas plus sur le sujet et pour cause le design pattern strategy rpond ce problme pos de manire trs efficace. Ce n'est
cependant pas le seul design pattern user de ce principe. Si vous souhaitez savoir comment, suivez la suite du tutoriel !
Plus d'informations ce sujet :
- le terme dsign pour ce concept se nomme OCP. On parle aussi de respecter le principe Open/Closed.
- Article en C++ sur Rgis Medina.
www.openclassrooms.com
25/68
conception objet va rduire drastiquement cette duplication. Il existe d'autres pratiques permettant de poser les bases sur
l'criture unique de code. Cette tape vient gnralement pendant l'criture du code et se retrouve donc en aval de la conception.
Mot clef associ : DRY
Pour aller encore plus loin :
- Ralisation d'interfaces avec dcouplage de l'implmentation : le pattern NVI, faq sur developpez.
- Commonalities & variation points : un sujet complexe de Coplien qui permet d'avoir une vision trs abstraite permettant de voir
plus loin que le simple mcanisme du polymorphisme.
Auteurs ayant activement particip la conception objet (non exhaustif):
- Jim Coplien.
- Robert C Martin.
- Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides (Gof)
Que de choses complexes ont t relates travers ce chapitre. Oui, vous tes fixs, respecter les diffrents postulats survols
ncessite une grosse part d'investissement intellectuel pour russir les satisfaire mais les bnfices sont sans appel : Vous
pourrez non seulement vous targuer d'avoir des codes qui ne seront plus remis en doute la moindre occasion et votre
application pourra voluer beaucoup plus sereinement. Aussi, les corrections de bogues auront beaucoup moins de chances de
se propager en cascade.
En route pour dcouvrir un patron de conception trs largement utilis aujourd'hui, "lobserver". La plupart des concepts
voqus vont tre utiliss, soyez l'aise avec ces grandes lignes et vous pourrez avoir un regard critique sur le "pourquoi" ils en
sont devenus des rfrences aujourd'hui. Ne vous contentez pas d'apprendre les patterns, assimilez les mcanismes sous-jacents
pour pourvoir les rutiliser lorsque vous tomberez sur un "os".
www.openclassrooms.com
26/68
Posons le problme
Soit un ascenseur, sa fonction premire est de monter ou de descendre des tages pour satisfaire des requtes de personnes.
Comme vous le savez peut tre, l'installation d'un ascenseur ne se rduit pas placer la cabine qui se dplacera dans un rail. De
multiples capteurs sont prsents pour que la cabine puisse se comporter idalement selon les situations, en voici une petite liste
:
Un capteur qui indique qu'un utilisateur ne se retrouve pas entre deux niveaux lorsque les portes doivent s'ouvrir.
Un capteur situ au niveau des portes pour savoir si quelqu'un n'est pas totalement rentr dans l'ascenseur.
Un capteur qui mesure la pression exerce sur les portes de l'ascenseur lorsqu'elles sont ouvertes. Ce dispositif permet
de s'assurer que rien ne sera cras si une trop forte rsistance s'oppose lors de la fermeture.
Cette liste n'est pas exhaustive, mais c'est pour vous montrer que sous son apparence simpliste, un ascenseur comporte des
mcanismes complexes (sans parler du traitement des requtes utilisateurs).
Les personnes ayant connaissance des automates programmables, des grafcets ou de la cration de schmas
lectriques complexes savent de quoi il est question.
Par cette brve introduction, rentrons maintenant dans le vif du sujet. Vous tes en charge de grer la bonne position de la cabine
de l'ascenseur. Pour cela, la cabine possde diverses informations comme son sens courant de dplacement et son tage actuel.
Le seul moyen pour vous de savoir quand l'ascenseur changera d'tage sera par lintermdiaire d'un capteur photosensible plac
sur l'ascenseur et dtectant un repre plac sur chaque tage. Comme un schma est souvent plus clair pour montrer de quoi il
est question, je vous propose le suivant :
Ds que le laser passe sur le repre, une variation va tre dtecte. Le capteur laser sait donc qu'un tage a t dcel.
La cabine doit maintenant mettre jour son nouvel tage courant.
Voici les lments qui sont donns pour rsoudre ce problme :
www.openclassrooms.com
27/68
Vous pouvez voir que le capteur est dans un thread, cela importe peu, concentrez-vous juste sur les oprations faire lorsqu'une
variation est dtecte.
En gros, ne touchez rien d'autre dans la mthode du thread que le corps de la boucle.
Avec ces lments de base, vous pouvez modifier les classes votre souhait, rajouter autant de mthodes que vous le souhaitez,
l'essentiel tant de faire diffuser l'information du capteur vers la cabine et de mettre jour l'tage de celle-ci. A vos brouillons !
www.openclassrooms.com
28/68
Voil une conception trs flexible, si on dcide de changer de capteur (par exemple un capteur aimantation), il suffira de crer
une nouvelle classe qui implmente l'interface. Le code pour la cabine ne changera pas.
Cependant cette approche a un problme et vous avez probablement remarqu que je n'en ai pas parl. Quel est le code exact
mettre dans la classe Cabine ? Puisqu'on doit scruter les changements d'tats du capteur, il faut pouvoir connatre en
permanence l'tat courant de celui-ci. Si vous avez devin, oui il s'agit bien d'une boucle qui s'occupe de vrifier la variation. Et
c'est encore une boucle infinie comme pour le capteur et bloquante de surcrot si on ne la place pas dans un autre thread.
L'implmentation est donc assez fastidieuse dans la cabine mais elle est possible. Linconvnient majeur est que malgr tout, on
va consommer des ressources processeur inutilement et il faudra ventuellement grer les problmes de synchronisme de thread.
Imaginez que le capteur dtecte toutes les 60mes de seconde, il est important que la cabine consulte au minimum 60 fois par
seconde aussi.
Le framework MFC de microsoft reprenait dans les grandes lignes cette approche pour grer les vnements. S'il parat
absurde aujourd'hui de procder de cette manire, l'poque rien ne choquait particulirement.
Retenez que cette solution n'est pas mauvaise en soit mais pour des raisons de performances/complexits, elle n'est pas
spcialement adapte nos besoins. C'est pourquoi nous l'abandonnons pour un autre raisonnement tout aussi trivial. Voyez
tout de mme la dmarche adopte pour russir pouvoir dfinir un comportement commun certains capteurs, ce qui fait que
l'criture du thread pour la cabine n'aurait pas chang si on "substituait" un autre capteur au mme comportement. Adopter cette
dmarche est la voie de la programmation rutilisable et flexible.
Seconde approche, avoir l'instance de l'ascenseur dans la classe capteur :
Une autre solution serait tout simplement de prvenir la cabine qu'une variation a t dtecte. Cette mthode est beaucoup plus
efficace et ne comporte pas de dfaut particulier. Il faut juste que la cabine puisse proposer une mthode sur laquelle on pourra
informer un changement d'tage. Comme mon habitude, je vous propose un diagramme UML de cette solution :
A vrai dire, si vous modlisez le problme de cette manire, vous vous passez d'une deuxime boucle comme traite dans la
premire approche et vous respectez par la mme occasion les grands principes objets. En fait pour tout vous dire, c'est une
implmentation concrte du design pattern observateur. Le seul inconvnient majeur de notre capteur est que si nous voulons
ajouter une autre classe qui souhaite savoir qu'une variation a eu lieu, il va falloir rajouter une rfrence explicite dans le capteur
laser et rajouter l'instruction dans notre boucle de thread.
www.openclassrooms.com
29/68
On pourrait imaginer que la cabine contrle le moteur. Admettons que pour offrir une scurit supplmentaire, ce moteur vrifie
indpendamment si l'ascenseur n'est pas un niveau hors des bornes (13me tage par exemple alors que le btiment ne possde
que 12 tages).
Dans ce cas prcis, on voit bien que le code du capteur va devoir tre modifi pour renseigner cette information au moteur.
Rappelez-vous qu'une bonne conception doit tre ferme autant que possible aux modifications sur le code existant. Si vous
avez suivi mon discours jusqu' prsent, vous pouvez dj entrevoir une solution l'aide d'interfaces, cette abstraction qui
permettra au capteur laser de signaler un nombre quelconque de classes sa variation sans modifier le code existant se
rapprochera du patron de conception observateur. Voyons maintenant de quoi il est question.
L'observateur
Ce que nous cherchons faire ici se rsume simplement avec un schma :
Transpos notre cas, lmetteur d'une information se rvlerait tre le capteur. Les rcepteurs de signalement seraient la cabine
ainsi que le moteur.
Je vous ai mis plusieurs couples de mots qui peuvent aider certaines personnes comprendre dans quel cas nous pouvons
appliquer le problme. Notez que les termes "Observable" et "Observateur" sont les mots les plus reprsentatifs car ils sont plus
abstraits (assez difficile juger tout de mme). Cependant vous ne serez pas en tort si vous utilisez les autres termes
.
Grce cette reprsentation, on peut directement constater quels termes vont apparatre dans notre conception pour manipuler
www.openclassrooms.com
30/68
des abstractions. Il y a cependant un dtail noter : Un annuaire, pour prvenir ses abonns qu'un changement d'tat existe (on
parle de notification), doit contenir la liste des intresss. Ce qui implique que l'annuaire doit proposer des mthodes permettant
n'importe qui de s'inscrire et de se dsinscrire aux moments souhaits.
Ces oprations communes tout ce qui pourrait tre observable ncessitent d'crire une implmentation. L'interface ne
sera donc pas adapte pour reprsenter un annuaire.
L'observateur lui, se contente de s'inscrire ce qui l'intresse, il a juste besoin d'tre prvenu lors d'une parution. Ce sera donc
une interface avec la mthode permettant de notifier.
Passons maintenant notre implmentation (je ne vais que m'attarder sur la partie code sur l'observateur et l'observable) :
Code : C++
#include <string>
#include <list>
#include <iostream>
using namespace std;
class IObservateur
{
public:
virtual void notifier() = 0;
};
class Observable
{
public:
void notifierObservateurs() const
{
// Notifier tous les observateurs
list<IObservateur*>::const_iterator end =
m_observateurs.end();
for (list<IObservateur*>::const_iterator it =
m_observateurs.begin(); it != end; ++it)
(*it)->notifier();
}
www.openclassrooms.com
31/68
};
private:
list<IObservateur*> m_observateurs;
et
sa
position
};
private:
bool m_detecteVariation;
int main()
{
Cabine instanceCabine;
Moteur instanceMoteur;
CapteurLaser capteurEtage;
capteurEtage.ajouterObservateur(&instanceCabine);
capteurEtage.ajouterObservateur(&instanceMoteur);
// On simule manuellement une variation (normalement c'est le
thread qui s'en charge)
www.openclassrooms.com
32/68
capteurEtage.notifierObservateurs();
// La cabine et le moteur ont reu une notification sur leur
mthode notifier()
capteurEtage.supprimerObservateur(&instanceMoteur);
cout << "Suppression du moteur dans les abonnes" << endl;
capteurEtage.notifierObservateurs();
return 0;
Et le java correspondant :
Code : Java
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
interface IObservateur
{
public void notifier();
}
class Observable
{
public Observable()
{
m_observateurs = new LinkedList<IObservateur>();
}
public void notifierObservateurs()
{
Iterator<IObservateur> it = m_observateurs.iterator();
// Notifier tous les observers
while(it.hasNext()){
IObservateur obs = it.next();
obs.notifier();
}
}
void ajouterObservateur(IObservateur observateur)
{
// On ajoute un abonn la liste en le plaant en premier
(implment en pull).
// On pourrait placer cet observateur en dernier
(implment en push, plus commun).
m_observateurs.add(observateur);
}
void supprimerObservateur(IObservateur observateur)
{
// Enlever un abonn a la liste
m_observateurs.remove(observateur);
}
}
www.openclassrooms.com
33/68
(normalement
c'est
le
capteurEtage.notifierObservateurs();
www.openclassrooms.com
34/68
Le principal dans tout ce que vous avez lu jusqu' prsent est le cheminement suivi. Si vous avez assimil cette dmarche c'est
que l'essentiel sur ce chapitre vous a t retransmis. Il est trs important de de suivre un raisonnement et de rflchir avant de
partir tte baisse dans le code. J'insiste encore sur le fait qu'une bonne conception est la clef de voute pour du code rutilisable,
extensible et robuste. Je rappelle que mon objectif premier sur ce cours n'est pas de vous apprendre utiliser les design
patterns mais d'apprendre concevoir d'une meilleure manire. Il existe tellement de sources sur internet qui traitent des
patrons de conception que je n'ai aucune prtention de faire mieux. Je dveloppe plutt la dmarche qui montre le rsultat d'une
conception trs rflchie partir d'ides concrtes.
Maintenant sachez qu'en Java vous ne serez pas obligs de devoir crire le code de l'Observateur et de l'Observable, ce design
pattern tant tellement utilis, il existe dans l'API de base :
Code : Java
// Exemple tir de wikipdia
class Signal extends Observable {
void setData(byte[] lbData){
setChanged(); // Positionne son indicateur de changement
notifyObservers(); // (1) notification
}
}
/*
On
cre
le
panneau
d'affichage
qui
implmente
l'interface
java.util.Observer.
Avec une mthode d'initialisation (2), on lui transmet le signal
observer (2).
Lorsque le signal notifie une mise jour, le panneau est redessin
(3).*/
class JPanelSignal extends JPanel implements Observer {
void init(Signal lSigAObserver) {
lSigAObserver.addObserver(this); // (2) ajout d'observateur
}
Le code varie un peu, on peut voir que la mthode update(...) correspond notre mthode notifier().
Mais attend ! Pourquoi il y a deux arguments supplmentaires dans la mthode
Pas de panique, un petit tour dans la documentation nous claire trs vite. Le premier argument correspond l'objet metteur de
la notification, alors que le second paramtre permet de passer des valeurs relatives cet vnement. Prenons pour exemple un
thermomtre qui lorsqu'il change sa temprature va envoyer la notification ses abonns. Oui mais on veut aussi obtenir quelle
temprature tait indique au thermomtre lorsqu'il a mis la notification. C'est le rle du second argument. Le premier argument
quant lui permet de reconnaitre qui a indiqu la notification si notre objet s'est abonn plusieurs sources (par exemple un
objet qui reoit la notification de deux thermomtres diffrents).
Connatre lmetteur peut vous sembler superflu mais dans la programmation vnementielle ce cas arrive assez souvent. Pour
ceux qui auraient un doute, la programmation vnementielle s'utilise trs souvent lorsque les logiciels fonctionnent avec une
interface graphique. Tout est vnement sur les interfaces et regardez sur une fentre l'apparence simple le nombre
d'vnements que l'on peut lancer :
www.openclassrooms.com
35/68
Il n'y a que la moiti des lments (encadrs en rouge) pouvant lancer des notifications sur mon exemple.
Sachez que chaque bouton, liste droulante ou autre utilise en principe une base de ce design pattern (mme si certains IDE
camouflent leur utilisation l'aide d'outils graphiques pour dessiner les fentres). C'est un patron de conception qui a un trs fort
taux d'utilisation.
Pour bien insister sur le fonctionnement de ce patron de conception, je vais vous proposer un exemple dans une application
graphique. Il sera ici question d'une interface de panier et vous verrez que son utilisation est moins intuitive qu'elle en a l'air.
Mais penchons nous sur le sujet : Une liste d'articles est reprsente et affiche dans un contrle avec un montant total n'est pas
calcul l'affichage. Un bouton permet destimer le cot total des composants et de le renseigner dans un champ prvu cet
effet. L'image qui suit devrait vous faciliter la comprhension :
IHM d'un panier. On peut voir le bouton qui permet de calculer le prix et de remplir le champ "Total".
Le problme avec le bouton, c'est qu'aucune capture d'vnement n'est associe dessus. Un bouton est la base un simple
composant comme le reste, inerte. Il est ncessaire de venir lui indiquer qu'un clic utilisateur va engendrer un comportement
spcifique. C'est l que le pattern observateur entre en jeu : l'utilisateur est donc observ pour savoir si une pression sur le
bouton a t exerce, tandis que le bouton devient un observateur. Autant vous le dire tout de suite, les programmes avec
interfaces graphiques camouflent la partie d'utilisateur observ (le framework ou le systme d'exploitation de charge de cet
www.openclassrooms.com
36/68
lment). Vous, en tant que programmeur, vous n'avez qu' vous proccuper du bouton et de le rendre observateur. La mthode
pour rendre un composant graphique observateur diffre selon le langage et les outils utiliss mais le principe reste toujours le
mme. Le rsultat sera donc le suivant :
Le bouton devient observateur d'un clic. Lorsque l'utilisateur appuiera sur le bouton, l'interface graphique va appeler l'vnement
que le bouton coute.
C'est ainsi que s'achve notre premire tude de cas. J'espre que ce chapitre aura t enrichissant pour vous. si tel est le cas,
j'aurai russi mon pari. Assurez vous de comprendre comment fonctionne ce patron de conception car il est utilis dans de
nombreux cas et mme dans d'autres patrons de conception plus complexes.
Si vous n'avez pas saisi quelque chose, relisez ce chapitre tte.
www.openclassrooms.com
37/68
Le tramway
Voici un cas un peu plus original que l'ascenseur vu prcdemment. J'ai nomm la barre un systme de contrle sur un tramway.
Ne vous affolez pas, je vais faire en sorte de bien vous guider pour comprendre ce que j'attends de ce systme.
L'exemple qui va suivre ne correspond pas forcment la ralit, inutile de me prciser que le tramway s'utilise
diffremment. Le but tant de comprendre le mini cahier des charges qui va suivre, pas de vous focaliser sur les
divergences par rapport la ralit.
Ce que l'on souhaite grer, c'est le comportement de ce tramway selon diverses situations. Par exemple, lorsque le tramway est
l'arrt, il est possible d'ouvrir les portes pour pouvoir sortir. Cette action n'est pas possible lorsque celui-ci est en dplacement.
Le tramway se voit donc offrir diffrentes interactions possibles qui n'auront pas les mmes incidences selon sa configuration.
Je vais sans plus attendre, donner les contraintes d'une manire plus formelle. Attaquons dans le vif du sujet avec un cahier des
charges trs simpliste. Bien quincompltes, ces spcifications seront suffisantes notre niveau.
Le cahier des charges :
Le tramway propose plusieurs actions possibles lorsqu'on est passager :
Un bouton arrt d'urgence pour arrter le tramway tout moment.
Un bouton pour pouvoir ouvrir les portes du tramway lorsqu'il est en station.
Un bouton pour demander l'arrt la prochaine station
Les stations quant elles, ont :
Un bouton pour prvenir le tramway qui arrive de s'arrter pour rcuprer des gens.
Un capteur qui indique au tramway qu'il est bien positionn pour s'arrter.
Un compteur qui envoie une impulsion au tramway pour indiquer qu'il peut repartir lors d'une attente respecte en
station.
Vous constaterez que les actions engendres par ces boutons devraient vous rappeler quelque chose. Aller, un petit effort !
Et oui, pour ceux qui auraient suivi, il s'agit d'vnements : cette notion est trs souvent rattachable au patron de conception
Observateur. Il va falloir revoir le concept s'il n'est pas maitris, vous n'y couperez pas.
Afin d'claircir le fonctionnement du tramway, je vous offre le schma suivant :
www.openclassrooms.com
38/68
Ne soyez pas surpris, sous des apparences trs simples, de tels systmes peuvent vite receler des difficults. Moi-mme, j'ai dj
sous-estim ce genre de problmes.
Bon, si vous lisez tte repose vous verrez que ce systme n'est pas difficile comprendre. Il y a mme beaucoup de
redondances dans lnonc mais je prfre tre clair sur ce que j'attends du systme. J'aurais pu complexifier le problme mais
pour viter de vous noyer sous une masse d'informations, on se contentera de ces lments. Vous verrez qu'on a dj de quoi
cogiter !
Pour notre tude de cas, on ne va pas s'occuper du fonctionnement des pupitres de commandes, je vais juste vous donner leurs
interfaces d'utilisation. Si vous ne le saviez pas, une interface permet aussi de pouvoir simuler des bouts de codes dont on ne
possde pas l'implmentation. C'est trs pratique si on veut simuler des environnements dont nous ne sommes pas toujours
matres. Les lments que je vous fournis sont les suivants :
www.openclassrooms.com
39/68
Diagramme de classes reprsentant les interfaces existantes. Ce n'est pas vous de les implmenter.
Le code fournir va juste concerner la classe Tramway.
Pour claircir le fonctionnement du tramway, je vous propose un diagramme d'tats-transitions. Il s'agit d'une version un peu
drive de sa reprsentation formelle pour faciliter sa lecture notre cas.
Arc
vnement(s) dclencheur(s)
Condition(s)
arretProchaineStationDeclenche()
OU arretDepuisStationDemande()
tramwayPositionne()
departAutorise()
portes fermes
demande d'arrt
departAutorise()
portes fermes
arretUrgenceDeclenche()
arretUrgenceDeclenche()
www.openclassrooms.com
40/68
arretUrgenceDeclenche()
arretUrgenceDeclenche()
tait en dplacement
arretUrgenceDeclenche()
tait en station
Diagramme d'tats-transitions du tramway.
Si on s'attarde un peu sur ce schma, il n'est pas difficile de le comprendre. Je vais prendre un exemple pour que vous compreniez
comment le lire.
Le tramway possde 4 tats qui reprsentent ses diffrentes situations :
Dplacement : Lorsque le tramway se dplace sur les rails.
Arrt imminent : Lorsque le tramway se dplace et qu'il doit s'arrter la prochaine station.
Arrt station : Lorsque le tramway est en train de dposer/rcuprer des gens en station.
Arrt d'urgence : Lorsque le tramway a le mode "arrt d'urgence" activ. Peut se produire tout moment.
Maintenant, admettons que le tramway se dplace sans que personne ne rclame l'arrt pour la prochaine station (on se situe
donc sur l'tat Dplacement). Tout coup, un utilisateur va demander un arrt pour tre dpos la station qui arrive.
L'vnement dclencheur arretProchaineStationDeclenche() est mis par lintermdiaire du bouton prvu cet effet. En jetant
un il au tableau, on peut voir que l'arc numro 1 attend cet vnement. Il est de ce fait, possible d'emprunter cet arc (ou
transition) pour se retrouver dans l'tat qui est point : Arrt imminent. Puisque je suis gnreux, je vais encore vous offrir un
schma sur ce qui se passe
:
Exemple du passage de l'tat "Dplacement" vers "Arrt imminent". L'arc est emprunt car la condition est satisfaite.
C'est toujours la mme logique adopter pour passer d'tat en tat. Avec un peu de bon sens vous pouvez facilement
comprendre ce diagramme, d'ailleurs je vous invite le faire
.
Pour les gens qui connaissent les automates tats, vous ne serez pas dboussol, c'est exactement le mme principe.
D'ailleurs, le diagramme d'tats-transitions que j'ai reprsent plus haut s'apparente plus un automate qu'autre chose.
Lanons nous
Pour bien commencer attaquer le problme, on va implmenter les vnements dclenchs ou capts par le tramway en crivant
les interfaces respectives. On pose le socle du systme pour ensuite nous intresser la partie essentielle de notre problme.
Ainsi, on ne sparpille pas et on respecte les contraintes intrinsques du systme. Le code crire est le suivant :
Code : C++
class IEventsPupitreTramway
{
www.openclassrooms.com
};
41/68
public :
virtual void arretUrgenceDeclenche() = 0;
virtual void arretProchaineStationDeclenche() = 0;
virtual void ouverturePorteDeclenche() = 0;
class IEventsPupitreStation
{
public :
virtual void arretDepuisStationDemande() = 0;
virtual void tramwayPositionne() = 0;
virtual void departAutorise() = 0;
};
class IEventsControleTramway
{
public :
virtual void ouvrirPortes() = 0;
virtual void fermerPortes() = 0;
virtual void stopperWagon() = 0;
virtual void demarrerWagon() = 0;
};
class Tramway : public IEventsPupitreTramway, public
IEventsPupitreStation
{
public:
virtual
virtual
virtual
virtual
virtual
virtual
void
void
void
void
void
void
arretUrgenceDeclenche()
arretProchaineStationDeclenche()
ouverturePorteDeclenche()
arretDepuisStationDemande()
tramwayPositionne()
departAutorise()
{
{
{
{
{
{
/*
/*
/*
/*
/*
/*
CODE
CODE
CODE
CODE
CODE
CODE
ICI
ICI
ICI
ICI
ICI
ICI
*/
*/
*/
*/
*/
*/
private:
IEventsControleTramway m_controlesWagon;
Code : Java
interface
{
public
public
public
}
IEventsPupitreTramway
interface
{
public
public
public
}
IEventsPupitreStation
interface
{
public
public
public
public
}
IEventsControleTramway
void arretUrgenceDeclenche();
void arretProchaineStationDeclenche();
void ouverturePorteDeclenche();
void arretDepuisStationDemande();
void tramwayPositionne();
void departAutorise();
void
void
void
void
ouvrirPortes();
fermerPortes();
stopperWagon();
demarrerWagon();
www.openclassrooms.com
}
}
}
}
}
}
void
void
void
void
42/68
ouverturePorteDeclenche()
arretDepuisStationDemande()
tramwayPositionne()
departAutorise()
{
{
{
{
/*
/*
/*
/*
CODE
CODE
CODE
CODE
ICI
ICI
ICI
ICI
*/
*/
*/
*/
}
}
}
}
La chose la plus intuitive consiste crire les comportements directement dans la classe Tramway. Pour ce cas prcis, il est mme
probablement judicieux de procder de cette manire. Avec une bonne apprhension du sujet, on peut facilement crire du code
propre qui corresponde au cahier des charges. Il va cependant falloir conserver des informations utiles pour que le tramway
adopte le bon comportement dans toutes les situations. Par exemple, lorsqu'il sort du mode arrt d'urgence, le wagon doit savoir
quelle tait son action prcdente (arrt d'un station ou en dplacement). Un autre exemple : le tramway doit savoir certains
moments si il doit s'arrter la prochaine station mais ce ne sont pas les seules choses sauvegarder.
Avec toutes ces astuces donnes, vous devriez tre mme de vous lancer dans une implmentation. Pour russir efficacement
cette preuve, respectez point par point cette tude de cas et vrifiez que chaque lment est respect la lettre.
Il est temps de vous mettre au travail ! (Mais qu'est-ce que c'est que ces mines dpites, plus vite que a moussaillon !
vous dcouragez pas, c'est en fait assez simple, pensez juste tous les cas.
Passons la correction. Une premire version du code qui correspondrait aux diffrentes contraintes pourrait tre :
Code : C++
#include <iostream>
using namespace std;
/**
* Implmentation de l'interface IEventsControleTramway pour pouvoir
faire des tests.
* On se contente juste de redfinir les oprations avec des
affichages.
*/
class ControlesTramway : public IEventsControleTramway
{
public:
virtual void ouvrirPortes() {cout << "Portes
ouvertes." <<
endl;}
virtual void fermerPortes() {cout << "Portes
fermes." <<
endl;}
virtual void stopperWagon() {cout << "Le
tramway
freine
jusqu' son arrt." << endl;}
virtual void demarrerWagon() {cout << "Le tramway se lance en
prenant de la vitesse." << endl;}
};
class Tramway : public IEventsPupitreTramway, public
IEventsPupitreStation
{
public:
/**
* Constructeur, on va considrer que le train commence l'tat
"arrt station".
*/
Tramway() : m_portesOuvertes(false), m_enDeplacement(false),
m_requeteProchaineStation(false),
m_arretUrgenceEnclenche(false)
{
m_controlesWagon = new ControlesTramway();
}
virtual ~Tramway()
{
// DTOR
delete m_controlesWagon;
www.openclassrooms.com
) Ne
43/68
}
/**
* L'arrt d'urgence doit savoir son tat prcdent pour adopter un
comportement.
* De plus, il faut savoir si l'vnement correspond l'activation
ou dsactivation de l'urgence.
*/
virtual void arretUrgenceDeclenche()
{
m_arretUrgenceEnclenche = !m_arretUrgenceEnclenche; //
On
dclare le nouvel tat
if(!m_arretUrgenceEnclenche)
{
if(m_portesOuvertes) // Les portes peuvent ne pas tre
ouvertes
m_controlesWagon->fermerPortes();
if(m_enDeplacement)
m_controlesWagon->demarrerWagon();
}
else
{
m_controlesWagon->stopperWagon();
}
}
virtual void arretProchaineStationDeclenche()
{
m_requeteProchaineStation = true;
cout << "Appel utilisateur d'un arrt
station depuis le wagon." << endl;
}
virtual void arretDepuisStationDemande()
{
m_requeteProchaineStation = true;
cout << "Appel utilisateur d'un arrt
station depuis la station." << endl;
}
la
prochaine
la
prochaine
/**
* L'ouverture des portes va s'assurer que le wagon n'est pas en
dplacement ou que le
* systme est en arrt d'urgence.
*/
virtual void ouverturePorteDeclenche()
{
if(m_arretUrgenceEnclenche || !m_enDeplacement)
{
m_controlesWagon->ouvrirPortes(); //
On
ouvre
les
portes
m_portesOuvertes = true;
}
else
{
cout << "Ouverture porte impossible." << endl;
}
}
/**
* Le capteur de station met un signal pour dclarer que le tramway
doit commencer sa phase
* d'arrt si des requtes sont satisfaire.
*/
virtual void tramwayPositionne()
{
if(m_requeteProchaineStation)
{
www.openclassrooms.com
44/68
m_controlesWagon->stopperWagon();
m_enDeplacement = false;
}
else
}
m_requeteProchaineStation = false;
cout << "Station passe sans s'arrter." << endl;
};
private:
IEventsControleTramway *m_controlesWagon;
bool m_portesOuvertes;
bool m_enDeplacement;
bool m_requeteProchaineStation;
bool m_arretUrgenceEnclenche;
int main()
{
Tramway tram;
// On va simuler les vnements en les appelant manuellement.
tram.arretDepuisStationDemande();
tram.departAutorise();
tram.tramwayPositionne();
tram.ouverturePorteDeclenche();
tram.departAutorise();
tram.tramwayPositionne();
tram.ouverturePorteDeclenche(); // Portes impossibles ouvrir
tram.tramwayPositionne();
tram.arretUrgenceDeclenche();
tram.ouverturePorteDeclenche();
tram.arretUrgenceDeclenche();
tram.arretProchaineStationDeclenche();
tram.tramwayPositionne();
}
return 0;
Code : Java
/**
* Implmentation de l'interface IEventsControleTramway pour pouvoir
faire des tests.
* On se contente juste de redfinir les oprations avec des
affichages.
*/
class ControlesTramway implements IEventsControleTramway
{
public void ouvrirPortes() {System.out.println("Portes
ouvertes.");}
public void fermerPortes() {System.out.println("Portes
fermes.");}
public void stopperWagon() {System.out.println("Le
tramway
freine jusqu' son arrt.");}
public void demarrerWagon() {System.out.println("Le tramway se
lance en prenant de la vitesse.");}
}
www.openclassrooms.com
45/68
}
class Tramway implements IEventsPupitreTramway,
IEventsPupitreStation
{
/**
* Constructeur, on va considrer que le train commence l'tat
arrt station.
*/
public Tramway()
{
// Ligne qui ncessite d'crire une implmentation de
IEventsControleTramway
m_controlesWagon = new ControlesTramway();
m_portesOuvertes = false;
m_enDeplacement = false;
m_requeteProchaineStation = false;
m_arretUrgenceEnclenche = false;
d'un
arrt
la
arrt
la
/**
* L'ouverture des portes va s'assurer que le wagon n'est pas en
dplacement ou que le
* systme est en arrt d'urgence.
*/
public void ouverturePorteDeclenche()
{
if(m_arretUrgenceEnclenche || !m_enDeplacement)
{
m_controlesWagon.ouvrirPortes(); // On ouvre les portes
m_portesOuvertes = true;
}
else
{
System.out.println("Ouverture porte impossible.");
}
www.openclassrooms.com
46/68
}
/**
* Le capteur de station met un signal pour dclarer que le tramway
doit commencer sa phase
* d'arrt si des requtes sont satisfaire.
*/
public void tramwayPositionne()
{
if(m_requeteProchaineStation)
{
m_controlesWagon.stopperWagon();
m_enDeplacement = false;
}
else
}
m_requeteProchaineStation = false;
System.out.println("Station passe sans s'arrter.");
private
private
private
private
private
IEventsControleTramway m_controlesWagon;
boolean m_portesOuvertes;
boolean m_enDeplacement;
boolean m_requeteProchaineStation;
boolean m_arretUrgenceEnclenche;
ouvrir
tram.tramwayPositionne();
tram.arretUrgenceDeclenche();
tram.ouverturePorteDeclenche();
tram.arretUrgenceDeclenche();
tram.arretProchaineStationDeclenche();
tram.tramwayPositionne();
}
www.openclassrooms.com
47/68
Souvenez vous qu'une majorit du temps est consacr la correction de bogues ou l'ajout de nouvelles fonctionnalits. Si
dans notre cas, un ajout de fonctionnalit va se traduire par une modification de la classe tramway, il est difficile de faire
autrement. On se concentrera donc plutt sur l'aspect maintenance. Je ne sais pas pour vous, mais moi je trouve que le code que
j'ai crit est difficile relire. En effet, les conditions et les tats qui sont sauvegards qui sont cods ne correspondent pas
explicitement mes spcifications. Pour moi, il est difficile de reprendre ce code car je dois m'imprgner de la logique du
dveloppeur qui n'est pas forcement vidente. Par exemple, regardez l'implmentation de la mthode ouverturePorteDemandee() :
Les conditions crites sont dissmines dans le sujet et pas si simple retrouver. Dans le cas d'un contexte simple comme le
tramway, lintrt de rendre plus comprhensible ce code est minime mais il faut se placer dans des cas plus complexes ou prvoir
une volution du systme.
C'est pourquoi il est judicieux de rflchir pour rendre notre code plus lisible. La solution qui serait trs utile dans notre cas serait
de calquer le code sur le diagramme d'tats-transitions. Comment ? La premire solution qui viendrait l'esprit serait d'utiliser une
numration, on stockerait ensuite les tats spcifiques du systme :
Code : C++
class Tramway
{
public :
enum Etat {DEPLACEMENT, ARRET_IMMINENT, ARRET_STATION,
ARRET_URGENCE};
private :
Etat etatCourant;
// ...
Code : Java
class Tramway
{
public enum Etat {DEPLACEMENT, ARRET_IMMINENT, ARRET_STATION,
ARRET_URGENCE};
private Etat etatCourant;
// ...
}
Le code pour exprimer les comportements du tramway deviennent ensuite beaucoup plus naturels et lisibles. La lecture s'en
retrouve grandement simplifie, je vous laisse juger par vous mme :
Code : C++
www.openclassrooms.com
48/68
www.openclassrooms.com
49/68
m_etatCourant = ARRET_IMMINENT;
cout << "Appel utilisateur d'un
station depuis le wagon." << endl;
}
arrt
la
prochaine
la
prochaine
/**
* L'ouverture des portes va s'assurer que le wagon n'est pas en
dplacement ou que le
* systme est en arrt d'urgence.
*/
virtual void ouverturePorteDeclenche()
{
switch(m_etatCourant)
{
case ARRET_URGENCE :
m_controlesWagon->ouvrirPortes(); //
On
ouvre
les
portes
m_portesOuvertes = true;
break;
case ARRET_STATION :
m_controlesWagon->ouvrirPortes(); //
portes
On
ouvre
les
m_portesOuvertes = true;
break;
default:
cout << "Ouverture porte impossible." << endl;
/**
* Le capteur de station met un signal pour dclarer que le tramway
doit commencer sa phase
* d'arrt si des requtes sont satisfaire.
*/
virtual void tramwayPositionne()
{
switch(m_etatCourant)
{
case ARRET_IMMINENT :
m_controlesWagon->stopperWagon();
m_etatCourant = ARRET_STATION; // Changement d'tat
break;
default:
cout << "Station passe sans s'arrter." << endl;
www.openclassrooms.com
pour
};
50/68
private:
IEventsControleTramway *m_controlesWagon;
Etat m_etatCourant;
Etat m_etatPrecedentArretUrgence;
bool m_portesOuvertes;
Code : Java
class Tramway implements IEventsPupitreTramway,
IEventsPupitreStation
{
public enum Etat {DEPLACEMENT, ARRET_IMMINENT, ARRET_STATION,
ARRET_URGENCE};
/**
* Constructeur, on va considrer que le train commence l'tat
arrt station.
*/
public Tramway()
{
// Ligne qui ncessite d'crire une implmentation de
IEventsControleTramway
m_controlesWagon = new ControlesTramway();
m_etatCourant = Etat.ARRET_STATION;
m_portesOuvertes = false;
}
public void arretUrgenceDeclenche() throws Exception
{
if(m_etatCourant != Etat.ARRET_URGENCE)
{
m_etatPrecedentArretUrgence = m_etatCourant;
m_etatCourant = Etat.ARRET_URGENCE;
}
else
{
m_etatCourant = m_etatPrecedentArretUrgence;
}
switch(m_etatCourant)
{
case ARRET_URGENCE :
m_controlesWagon.stopperWagon();
break;
case DEPLACEMENT :
if(m_portesOuvertes)
m_controlesWagon.fermerPortes();
m_controlesWagon.demarrerWagon();
break;
case ARRET_STATION :
m_controlesWagon.fermerPortes();
break;
default:
m_controlesWagon.stopperWagon();
// Il faudrait crer un nouveau type d'exception qui
drive d'Exception.
throw new Exception("Etat incohrent sur le bouton
arrt d'urgence");
}
}
public void arretProchaineStationDeclenche()
www.openclassrooms.com
51/68
m_etatCourant = Etat.ARRET_IMMINENT;
System.out.println("Appel
utilisateur
prochaine station depuis le wagon.");
}
public void arretDepuisStationDemande()
{
m_etatCourant = Etat.ARRET_IMMINENT;
System.out.println("Appel
utilisateur
prochaine station depuis la station.");
}
d'un
arrt
la
d'un
arrt
la
/**
* L'ouverture des portes va s'assurer que le wagon n'est pas en
dplacement ou que le
* systme est en arrt d'urgence.
*/
public void ouverturePorteDeclenche()
{
switch(m_etatCourant)
{
case ARRET_URGENCE :
m_controlesWagon.ouvrirPortes(); //
On
ouvre
les
portes
m_portesOuvertes = true;
break;
case ARRET_STATION :
m_controlesWagon.ouvrirPortes(); //
portes
On
ouvre
les
m_portesOuvertes = true;
break;
default:
System.out.println("Ouverture porte impossible.");
}
/**
* Le capteur de station met un signal pour dclarer que le tramway
doit commencer sa phase
* d'arrt si des requtes sont satisfaire.
*/
public void tramwayPositionne()
{
switch(m_etatCourant)
{
case ARRET_IMMINENT :
m_controlesWagon.stopperWagon();
m_etatCourant = Etat.ARRET_STATION; //
Changement
d'tat
break;
default:
System.out.println("Station passe sans s'arrter.");
}
public void departAutorise()
{
m_controlesWagon.fermerPortes();
m_controlesWagon.demarrerWagon();
if(m_etatCourant == Etat.ARRET_STATION) //
pour la prochaine station
www.openclassrooms.com
Aucune
requte
52/68
private
private
private
private
IEventsControleTramway m_controlesWagon;
boolean m_portesOuvertes;
Etat m_etatCourant;
Etat m_etatPrecedentArretUrgence;
Je sais pas pour vous, mais je trouve ce code beaucoup plus simple apprhender. Mme si le nombre de lignes augment
quantit de code augmente, ce handicap est largement compens par sa facilit de relecture. Par la mme occasion, certains
boolens ont disparu pour ne garder que les tats du tramway. Notez donc qu'en changeant au minimum son approche, on se
permet de se dcoller d'une implmentation trop cible et on diminue les futurs prises de ttes.
De plus, il est maintenant trs
facile de prendre en compte un nouvel tat en rajouter le cas dans un switch.
Il y a tout de mme quelque chose de gnant dans ce code ne refltant toujours pas notre diagramme d'tats-transition de
manire naturelle. La retranscription fait surtout ressortir les arcs et non les tats en eux-mmes. Par exemple, il est difficile de
savoir quand notre tramway se retrouve dans un tat spcifique. En plus, si de nouveaux tats ou arcs apparaissent, la classe
Tramway va vite devenir norme et tre difficilement maintenable.
Bon et alors tu proposes quoi ? De dporter le code en dehors de cette classe ?
www.openclassrooms.com
53/68
En fait, c'est la seule possibilit pour un tat de de dire qu'il va laisser place un autre. Il est oblig de remonter vers l'automate
pour que ce dernier puisse changer vers le nouvel tat pointer. Si vous avez compris le principe, alors c'est gagn ! Voila ce que
donne le diagramme UML complet :
www.openclassrooms.com
54/68
Un lien a t rajout l'automate permettant un tat de pouvoir agir sur les contrles du tramway.
Les plus attentionns auront remarqu qu'il y a un problme sur le diagramme. L'opration changerEtat() propose un
service qui ne doit pas tre accessibles en dehors du cadre de l'automate et de ses tats. Le Tramway ne doit pas
pouvoir changer un tat de l'automate, seuls ces tats sont habilits faire ce changement.
Sans rentrer dans les dtails, il existe des moyens plus ou moins efficaces pour rsoudre cet ala. Voici une liste des possibilits :
Laisser le code tel quel et rajouter un commentaire pour indiquer que l'opration est rserve pour un usage interne. Cette
solution est viter autant que possible.
On peut utiliser l'amiti pour donner des accs privilgis certaines classes. Cet artifice existe en C++ mais pas en Java
par exemple.
On peut dcouper l'application en packages ou namespaces et indiquer que certaines oprations sont isoles dans ces
sous-couches et ne sont pas connues ailleurs. Cette fonctionnalit n'est pas prsente dans tous les langages non plus.
Enfin, on peut se servir du design pattern Visiteur qui peut s'utiliser dans tous les langages mais engendre une complexit
supplmentaire. Cependant, ce pattern n'est pas prvu pour rsoudre ce problme la base.
Pour viter une surcharge du code, je me contenterai de la premire solution mais elle est proscrire autant que possible. Si je
devais choisir une solution parmi les suivantes, mon choix se porterait sur l'amiti pour le code C++ et un dcoupage en
packages pour la solution Java (avec l'automate et ses tats dans un mme package isols du reste et dont l'opration
changerEtat() serait en visibilit package).
Nous voil enfin arrivs l'implmentation. Je place le code dans une balise secret pour viter de rallonger la page. En effet, la
quantit de code augmente significativement avec cette solution.
Code C++ : Pour des raisons de dclarations anticipes obligatoires, je n'ai pas pu regrouper le code dans un seul fichier. J'ai
donc donn le code dcoup pour chaque fichier d'entte et du corps correspondant :
www.openclassrooms.com
55/68
www.openclassrooms.com
void
void
void
void
void
void
56/68
arretDepuisStationDemande() ;
tramwayPositionne();
departAutorise();
arretUrgenceDeclenche();
arretProchaineStationDeclenche();
ouverturePorteDeclenche();
private :
AutomateTramway *m_contexte;
IEventsControleTramway *m_controlesWagon;
};
#endif // TRAMWAY_H_INCLUDED
// ====================================
// FICHIER AutomateTramway.h
// ====================================
#ifndef AUTOMATETRAMWAY_H_INCLUDED
#define AUTOMATETRAMWAY_H_INCLUDED
#include "Interfaces.h"
class AutomateTramway : public IEventsPupitreStation, public
IEventsPupitreTramway
{
public :
AutomateTramway(IEventsControleTramway *referenceTram);
IEventsControleTramway& getControlesTramway();
void changerEtat(EtatTramway *etat);
virtual
virtual
virtual
virtual
virtual
virtual
void
void
void
void
void
void
arretUrgenceDeclenche();
arretProchaineStationDeclenche() ;
ouverturePorteDeclenche();
arretDepuisStationDemande();
tramwayPositionne();
departAutorise();
private :
IEventsControleTramway *m_refCtrlTramway;
EtatTramway *m_possedeEtat;
};
#endif // AUTOMATETRAMWAY_H_INCLUDED
// ====================================
// FICHIER EtatDeplacement.h
// ====================================
#ifndef ETATDEPLACEMENT_H_INCLUDED
#define ETATDEPLACEMENT_H_INCLUDED
#include "Interfaces.h"
class EtatDeplacement : public EtatTramway
{
public :
virtual void arretProchaineStationDemandee(AutomateTramway
&c);
virtual void arretDepuisStationDemande(AutomateTramway &c);
virtual void arretUrgenceDeclenche(AutomateTramway &c);
virtual void tramwayPositionne(AutomateTramway &c);
virtual void ouverturePorteDeclenche(AutomateTramway &c);
virtual void departAutorise(AutomateTramway &c);
};
#endif // ETATDEPLACEMENT_H_INCLUDED
// ====================================
// FICHIER EtatArretStation.h
// ====================================
#ifndef ETATARRETSTATION_H_INCLUDED
#define ETATARRETSTATION_H_INCLUDED
www.openclassrooms.com
57/68
#include "Interfaces.h"
class EtatArretStation : public EtatTramway
{
public :
EtatArretStation();
&c);
void
void
void
void
arretDepuisStationDemande(AutomateTramway &c);
arretUrgenceDeclenche(AutomateTramway &c);
departAutorise(AutomateTramway &c);
ouverturePorteDeclenche(AutomateTramway &c);
};
#endif // ETATARRETSTATION_H_INCLUDED
// ====================================
// FICHIER EtatArretImminent.h
// ====================================
#ifndef ETATARRETIMMINENT_H_INCLUDED
#define ETATARRETIMMINENT_H_INCLUDED
#include "Interfaces.h"
class EtatArretImminent : public EtatTramway
{
public :
virtual void arretUrgenceDeclenche(AutomateTramway &c);
virtual void tramwayPositionne(AutomateTramway &c);
virtual void ouverturePorteDeclenche(AutomateTramway &c);
virtual void arretProchaineStationDemandee(AutomateTramway
&c);
virtual void departAutorise(AutomateTramway &c);
virtual void arretDepuisStationDemande(AutomateTramway &c);
};
#endif // ETATARRETIMMINENT_H_INCLUDED
// ====================================
// FICHIER EtatArretUrgence.h
// ====================================
#ifndef ETATARRETURGENCE_H_INCLUDED
#define ETATARRETURGENCE_H_INCLUDED
#include "Interfaces.h"
class EtatArretUrgence : public EtatTramway
{
public :
EtatArretUrgence(EtatTramway *etatPrecedent) ;
&c);
virtual
virtual
virtual
virtual
void
void
void
void
arretUrgenceDeclenche(AutomateTramway &c);
tramwayPositionne(AutomateTramway &c);
departAutorise(AutomateTramway &c);
arretProchaineStationDemandee(AutomateTramway
private :
EtatTramway *m_etatPrecedent;
bool m_portesOUvertes;
};
#endif // ETATARRETURGENCE_H_INCLUDED
www.openclassrooms.com
58/68
pointeur
Tramway::~Tramway()
{
delete m_controlesWagon;
delete m_contexte;
}
IEventsControleTramway& Tramway::controles() { return
*m_controlesWagon; }
void Tramway::arretDepuisStationDemande()
{
m_contexte->arretDepuisStationDemande();
std::cout << "Appel utilisateur d'un arrt
station depuis la station." << std::endl;
}
la
prochaine
www.openclassrooms.com
59/68
www.openclassrooms.com
60/68
c.getControlesTramway().stopperWagon();
c.changerEtat(new EtatArretUrgence(this));
"AutomateTramway.h"
"EtatArretUrgence.h"
"EtatArretImminent.h"
"EtatDeplacement.h"
EtatArretStation::EtatArretStation() :
m_demandeProchainArret(false)
{
}
void
EtatArretStation::arretProchaineStationDemandee(AutomateTramway
&c)
{
m_demandeProchainArret = true;
}
void EtatArretStation::arretDepuisStationDemande(AutomateTramway
&c)
{
m_demandeProchainArret = true;
}
void EtatArretStation::arretUrgenceDeclenche(AutomateTramway &c)
{
c.getControlesTramway().stopperWagon();
c.changerEtat(new EtatArretUrgence(this));
}
void EtatArretStation::departAutorise(AutomateTramway &c)
{
c.getControlesTramway().fermerPortes();
c.getControlesTramway().demarrerWagon();
if(m_demandeProchainArret)
c.changerEtat(new EtatArretImminent());
else
c.changerEtat(new EtatDeplacement());
}
void EtatArretStation::ouverturePorteDeclenche(AutomateTramway &c)
{
c.getControlesTramway().ouvrirPortes();
}
void EtatArretStation::tramwayPositionne(AutomateTramway &c) { /*
RIEN */ }
// ====================================
// FICHIER EtatArretImminent.cpp
// ====================================
www.openclassrooms.com
61/68
#include "EtatArretImminent.h"
#include "AutomateTramway.h"
#include "EtatArretStation.h"
#include "EtatArretUrgence.h"
#include <iostream>
void EtatArretImminent::arretUrgenceDeclenche(AutomateTramway &c)
{
c.getControlesTramway().stopperWagon();
c.changerEtat(new EtatArretUrgence(this));
}
void EtatArretImminent::tramwayPositionne(AutomateTramway &c)
{
c.getControlesTramway().stopperWagon();
c.changerEtat(new EtatArretStation());
}
void EtatArretImminent::ouverturePorteDeclenche(AutomateTramway
&c)
{ std::cout << "Ouverture porte impossible." << std::endl; }
void
EtatArretImminent::arretProchaineStationDemandee(AutomateTramway
&c) { /* RIEN */ }
void EtatArretImminent::departAutorise(AutomateTramway &c) { /*
RIEN */ }
void EtatArretImminent::arretDepuisStationDemande(AutomateTramway
&c) { /* RIEN */ }
// ====================================
// FICHIER EtatArretUrgence.cpp
// ====================================
#include "EtatArretUrgence.h"
#include "AutomateTramway.h"
EtatArretUrgence::EtatArretUrgence(EtatTramway *etatPrecedent) :
m_etatPrecedent(etatPrecedent),
m_portesOUvertes(false)
{
}
void EtatArretUrgence::arretUrgenceDeclenche(AutomateTramway &c)
{
if(m_portesOUvertes)
c.getControlesTramway().fermerPortes();
c.getControlesTramway().demarrerWagon();
c.changerEtat(m_etatPrecedent);
}
void EtatArretUrgence::tramwayPositionne(AutomateTramway &c) { /*
RIEN */ }
void EtatArretUrgence::departAutorise(AutomateTramway &c) { /*
RIEN */ }
void
EtatArretUrgence::arretProchaineStationDemandee(AutomateTramway
&c) { /* RIEN */ }
void EtatArretUrgence::arretDepuisStationDemande(AutomateTramway
&c) { /* RIEN */ }
void EtatArretUrgence::ouverturePorteDeclenche(AutomateTramway &c)
{
c.getControlesTramway().ouvrirPortes();
m_portesOUvertes = true;
}
www.openclassrooms.com
62/68
// ====================================
// FICHIER main.cpp
// ====================================
#include "Tramway.h"
using namespace std;
int main()
{
Tramway tram;
// On va simuler les vnements en les appelant manuellement.
tram.arretDepuisStationDemande();
tram.departAutorise();
tram.tramwayPositionne();
tram.ouverturePorteDeclenche();
tram.departAutorise();
tram.tramwayPositionne();
tram.ouverturePorteDeclenche(); // Portes impossibles ouvrir
tram.tramwayPositionne();
tram.arretUrgenceDeclenche();
tram.ouverturePorteDeclenche();
tram.arretUrgenceDeclenche();
tram.arretProchaineStationDeclenche();
tram.tramwayPositionne();
}
return 0;
Code Java :
Secret (cliquez pour afficher)
Code : Java
interface
{
public
public
public
}
IEventsPupitreTramway
interface
{
public
public
public
}
IEventsPupitreStation
interface
{
public
public
public
public
}
IEventsControleTramway
interface
{
public
public
public
public
public
EtatTramway
void arretUrgenceDeclenche();
void arretProchaineStationDeclenche();
void ouverturePorteDeclenche();
void arretDepuisStationDemande();
void tramwayPositionne();
void departAutorise();
void
void
void
void
void
void
void
void
void
ouvrirPortes();
fermerPortes();
stopperWagon();
demarrerWagon();
arretProchaineStationDemandee(AutomateTramway c);
arretDepuisStationDemande(AutomateTramway c);
tramwayPositionne(AutomateTramway c);
departAutorise(AutomateTramway c);
arretUrgenceDeclenche(AutomateTramway c);
www.openclassrooms.com
63/68
la
prochaine station depuis la station.");
}
public void tramwayPositionne() {
m_contexte.tramwayPositionne(); }
public void departAutorise() { m_contexte.departAutorise(); }
public void arretUrgenceDeclenche()
{m_contexte.arretUrgenceDeclenche(); }
public void arretProchaineStationDeclenche()
{
m_contexte.arretProchaineStationDeclenche();
System.out.println("Appel
utilisateur
d'un
arrt
la
prochaine station depuis le wagon.");
}
public void ouverturePorteDeclenche() {
m_contexte.ouverturePorteDeclenche(); }
www.openclassrooms.com
64/68
m_possedeEtat.arretProchaineStationDemandee(this);}
public void ouverturePorteDeclenche() {
m_possedeEtat.ouverturePorteDeclenche(this);
}
public void arretDepuisStationDemande() {
m_possedeEtat.arretDepuisStationDemande(this);
}
public void tramwayPositionne() {
m_possedeEtat.tramwayPositionne(this); }
public void departAutorise() {
m_possedeEtat.departAutorise(this); }
tramwayPositionne(AutomateTramway c) { /* RIEN */ }
departAutorise(AutomateTramway c) { /* RIEN */ }
arretProchaineStationDemandee(AutomateTramway c) {
arretDepuisStationDemande(AutomateTramway c) { /*
www.openclassrooms.com
65/68
c.getControlesTramway().ouvrirPortes();
m_portesOUvertes = true;
www.openclassrooms.com
66/68
c.getControlesTramway().ouvrirPortes();
les
appelant
impossibles
tram.arretProchaineStationDeclenche();
tram.tramwayPositionne();
La quantit de code a vraiment grimp. Premire constatation : La mise en place du design pattern a souvent pour consquence
d'engendrer des classes en plus, complexifiant le code. L'avantage principal ici est qu'en connaissant comment s'utilise le patron
de conception tat, on peut facilement trouver et corriger les problmes car les classes ont des rles trs spcifiques et bien
dtermins. L'autre aspect dterminant que l'on peut soulever, est le fait qu'on puisse traduire de manire fidle un automate
tats ou un diagramme d'tats-transitions. Si une nouvelle transition ou un nouvel tat apparait, il est simple de le rajouter.
Si vous trouvez que le code du dessus vous parait difficile, voil une petite aide sur la procdure. Vous verrez qu'en fait c'est trs
simple partir de mon diagramme d'tats-transitions de retranscrire le modle. Si on reprend le croquis :
Rappel du diagramme.
Il suffit de se placer sur un tat particulier. Prenons par exemple l'tat "Arrt station" qui possde les origines des arcs "3", "4",
et "7". En regardant le tableau correspondant dans la premire partie, on peut voir les vnements dclencheurs sur chacun de
www.openclassrooms.com
67/68
ces arcs. Chaque arc implique d'implmenter une opration pour un vnement donn. Si on suit notre exemple, l'arc "3" possde
comme vnement dclencheur departAutorise(), on devra donc crire le comportement de cette opration dans l'tat "Arrt
station". Dans cette opration, le changement d'tat vers "Arrt imminent" sera effectuer si les conditions sont respectes. En
regardant le code, la retranscription apparait trs distinctement.
Pour conclure sur le design pattern tat, retenez que c'est un modle intressant pour reprsenter un problme spcifique. C'est
un bon exemple qui vous montre que les patrons de conception ne sont pas tous synonymes de rponses des problmes
quotidiens. Maintenant, si vous tombez sur ce genre de situation, son implmentation ne sera plus un casse tte. N'apprenez pas
la solution par cur, sachez quelle problmatique rpond ce pattern et une recherche sur internet vous indiquera la marche
suivre. Le but est justement de pouvoir rechercher facilement ces modles. D'ailleurs, c'est ce que moi mme j'ai fait pour la
rdaction de ce chapitre.
Ouf, en voil un chapitre plutt charg ! Si la lecture vous a t profitable je n'en serai que plus heureux. Nous allons revenir sur
des patrons de conception utilisation plus courants.
La suite pour bientt...
Pas de conclusion pour l'instant
www.openclassrooms.com