Je vous présente ici un pattern essentiel pour développer une
application robuste, durable et maintenable : Inversion du contrôle
(ou Inversion of Control soit encore IOC). Cette fois comme
dans les quatres autres articles de cette série du cours UML, je ne vous
fournirai pas de diagramme UML représentant la structure globale du design
pattern, tout simplement parce qu'il existe de trop nombreuses situations et
qu'un seul diagramme ne représenterait pas grand chose. Il n'en reste
pas moins que ce tutoriel est un tutoriel sur les designs patterns relatif à
l'implémentation objet de bonne qualité.
Présentation
La notion d'objet est née de notre observation de notre environnement,
nous manipulons des objets qui ont leur propre manière d'influencer leur
environnement en fonction de propriétés qui leurs sont intrinsèques.
Par exemple appliquer et faire glisser un stylo sur une feuille de papier n'a
pas le même effet que d'en faire autant avec une gomme.De même,
le stylo à bille rouge n'a pas exactement le même effet sur notre
feuille que le stylo à bille noir. Pour autant leur comportement diffère
peu et on est alors tenté de factoriser ce qui peut l'être, et
nous le faisons grâce à la notion d'héritage chère
à l'objet. Mais l'objet réagit aussi indépendamment de
sa propre structure interne, soumis à des forces qui lui sont extérieures.
Le stylo de tout à l'heure aura la même réaction que la
gomme si je le lâche au dessus du sol : il tombe. De la même manière,
bien que sachant marcher, si vous me placez sur une plaque de verglas, au mieux
j'aurais une démarche de canard, au pire je ressemblerais à un
pingouin qui ferait de la luge sur la banquise (surtout les jours où
je me rends au bureau).
Comme nous le voyons donc, l'objet nous a permis "d'intérioriser"
les comportements qui lui sont propres mais ne nous dispense pas de nous intéresser
à son environnement.
Etude
L'environnement de l'objet est suffisamment important pour que nous lui pretions
une attention toute particulière. Malheureusement, d'une part habitués
aux langages impératifs, soit par de nombreuses années d'expérience,
soit tout simplement parce que c'est beaucoup plus facile à appréhender,
nous avons, nous développeurs, la fâcheuse tendance à vouloir
maîtriser celui-ci de manière complète. En effet, combien
de fois avez-vous écrit lors des premiers jets d'une application gérant
sa persistance à l'aide d'un SGBD : Class.forName("nom.du.Driver')
? Parenthèse pour les non javayeurs, ce Class.forname(String) demande
simplement au classloader de charger la classe en mémoire et éventuellement
d'en instancier une, c'est à dire de se préparer à utiliser
cette classe. Nous avons dès lors décidé de maîtriser
notre environnement en définissant d'avance le Driver donc le SGBD vers
lequel nous nous tournons pour la persistance. Bien entendu, dans ce cas particulier,
il existe suffisamment d'outils aujourd'hui pour que rapidement vous changiez
cette ligne de code en quelque chose de plus souple basé sur le container
de votre application (c'est particulièrement facile pour une application
web). Pourtant vous êtes bien face à un problème majeur
qui va rendre votre développement très rigide : votre classe essaie
de maîtriser son environnement plutôt que de s'y adapter.
Imaginez maintenant que je vous demande d'allumer la bougie, vous déduisez
de ma requête qu'il existe probablement une bougie dans votre environnement,
qu'il existe de quoi l'allumer aussi, et comme vous savez aussi bien gratter
une allumette que mettre la flamme sous la mèche, vous pouvez agir sur
un environnement à priori inconnu. Il n'en reste pas moins que vous n'avez
pas décidé suite, à ma requète, de prendre votre
manteau et votre porte-monnaie pour aller acheter la bougie et les allumettes.
Au pire, si l'un des éléments vous semble manquer, vous me demanderez
certainement où le trouver. Plus concrètement, en objet, allumer
une bougie ne se traduit pas sous la forme créer la bougie, créer
les allumettes, puis allumez celle-là avec celles-ci. Et dans le pire
des cas, on s'est retourné vers le container pour lui demander ce qu'il
nous manquait pour mener notre tâche à bien. Il devrait en être
de même dans nos applications, or il n'en est rien ! Relisez quelques
uns de vos codes, vous verrez comme c'est naturel pour le développeur
que vous êtes d'avoir cette attitude de maîtrise de son environnement.
Implémentation
Oui bien qu'aucune version concrète en UML ne vous soit proposée
dans ce tutoriel, je vais quand même prendre un exemple concret de la
vie courante que je rapprocherai d'une implémentation objet pour que
les idées soient plus claires sur ce concept d'inversion du contrôle.
Voyons donc les points positifs de ce pattern sur vos développements.
Sécurité ou robustesse
Vous fournissez à vos objets leur environnement, soit, mais en quoi
est-ce plus robuste ? Quelle sécurité va en ressortir ? La première
chose évidente : votre objet n'a accès qu'à ce que le container
de celui-ci, c'est à dire celui qui l'a instancié et qui donc
le détruira ce qu'il veut bien lui donner. Il est donc impossible, parce
que le design l'interdit, que l'objet accède à une donnée
à laquelle il n'est pas habilité. Vous pouvez donc vous concentrer
sur le comportement de celui-ci sans vous préoccuper de la sécurité
qui si elle est diffuse dans les différents objets, sera difficile à
appréhender, donc à maintenir et même à vérifier.
Or comme chacun le sait, la sécurité d'une application est aussi
vétuste que le plus vétuste des composants qui la compose. Il
nous faut donc pour ne pas risquer de créer artificiellement des trous
de sécurité, concentrer la logique de sécurité dans
une partie bien distincte de l'application et disposer d'une architecture respectant
cette sécurité qui lui est imposée.
Pour imager mon propos, voici un exemple. Etudions l'organisation d'une petite
entreprise de peinture en bâtiment dont nous considérons qu'elle
a trois niveaux hiérachiques : les gestionnaires, les superviseurs et
les ouvriers. Pour s'assurer de la sécurité tant de l'ouvrier
lui-même que de la sécurité des gestionnaires face à
la loi, il est important que l'ouvrier quand il travaille, n'utilise que des
outils respectant certaines conventions et de la peinture tout aussi normalisée.
En effet, l'ouvrier ne doit pas monter sur une échelle ne disposant pas
d'une stabilité suffisante, et il ne faut pas qu'il peigne à la
peinture au plomb. Afin d'être certain que ceci n'arrivera pas, ne serait-ce
qu'une fois, parce que c'est plus simple, il faut à tout prix fournir
à notre ouvrier tous les outils dont il a besoin pour travailler. Cela
va de l'échelle à la peinture en passant par le pinceau. Il doit
avoir l'habitude qu'on lui fournisse tout et savoir le cas échéant
comment réclamer ce qui peut lui manquer. Et c'est son superviseur qui
s'occupe de lui fournir tout çà. Quant au chef de chantier, il
a donc un rôle essentiel, celui d'acheter la fourniture (attention je
ne réduis pas son travail à cette seule tâche bien entendu)
et pour ce faire, il doit disposer d'un budget. Mais ce n'est pas lui qui fixe
le budget, il lui est alloué par les gestionnaires qui auront à
coeur de maîtriser les dépenses et donc de gérer ce budget
de manière intelligente. A défaut, c'est la sécurité
de toute l'entreprise qui est en danger. Vous voyez maintenant comment on inverse
le contrôle ? On fait simplement en sorte que le container fournisse l'information
au contenu. C'est simple mais c'est efficace.
Adaptabilité ou évolutivité
Alors maintenant vous voyez bien en quoi on a rendu le code plus robuste et
amélioré la sécurité intrinsèque de l'application
mais franchement, l'inversion du contrôle peut-elle améliorer l'évolutivité
de celle-ci ? C'est beaucoup pour un seul pattern, non ? Et pourtant, le fait
de permettre à l'environnement d'être celui qu'il est plutôt
que d'être celui que l'objet voudrait lui imposer est un gage d'adaptabilité
pour celui-ci. En particulier, imaginez-vous possible pour un objet d'évoluer
s'il est bien enfermé dans un cocon qu'il se construit à chaque
fois qu'il existe ? C'est antinomique, pour évoluer, il faut qu'il se
confronte à divers environnements. Et c'est bien parce que vous concevez
votre application avec cette idée en tête : je ne connais pas mon
environnement mais quand j'y trouverai çà, çà et
çà je saurai alors faire telle chose, que vous la concevez d'ores
et déjà sous une forme évolutive. Dans l'exemple de la
bougie, tout ce que j'ai besoin de faire pour allumer une bougie, c'est de trouver
des allumettes et une bougie.
Reprenons l'exemple de notre entreprise, si la technologie des pinceaux évolue,
l'ouvrier habitué à trouver son pinceau tous les matins ne sera
pas fondamentalement perturbé par la présence de ce tout dernier
modèle, il continuera à faire les gestes qui constituent l'héritage
de son métier et de son expérience. Par contre, s'il est habitué
à acheter son pinceau toujours au même endroit, il est probable
qu'il ne change pas de technologie de sitôt car son métier n'est
pas, de parcourir les magasins à la recherche de la dernière techno,
d'autant qu'il n'en a pas le temps. Et pour notre objet (java ou autre) c'est
pire, il fabrique lui-même l'équivalent du pinceau, changer de
pinceau revient donc à réécrire la classe !
Un petit bémol
L'inversion du contôle doit toutefois, comme toujours, être l'objet
d'une critique, l'utiliser absolument reviendrait alors à se lancer dans
un antipattern. Il est par exemple inutile de demander au container un logger
alors qu'il existe aujourd'hui des factories permettant d'obtenir un logger
de manière simple et spécifique pour une classe. Vouloir utiliser
ce pattern dans ce cas reviendrait à vouloir aussi donner à notre
ouvrier toute sa garde robe (donc à priori à lui demander d'arriver
nu tous les jours sur le chantier !) Il existe en effet quelques petits services
techniques pour lesquels il est de loin préférable de fabriquer
une factory générique qu'utiliseront tous les objets.
Exemple d'implémentation
Pour rendre encore plus concret en terme de langage objet mon propos, voici
un exemple assez générique d'implémentation en UML de ce
pattern. Comme cet article fait partie d'une petite série de quatre articles
démarrée avec le design pattern du
GOF "le monteur", l'implémentation java de ce type de pattern
sera présentée plus tard.
Conclusion
J'espère vous avoir donné envie dans ce premier volet du cours
sur les design pattern sur lesquels repose Avalon,
d'une part d'utiliser ce framework et d'autre part, surtout, de continuer à
vous documenter autour des patterns objets qui n'ont, comme vous avez pu le
constater, pas toujours une représentation UML sous la forme d'un diagramme
de classes, mais forment néanmoins un catalogue de "bonnes pratiques".
Copyright (c) 2003 Sébastien MERIC. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation; with no Invariant Sections, with the Front-Cover Texts being Design pattern d'Avalon - IOC, and Back-Cover Texts being Ce document à été écrit pour la communauté de développeurs francophones "www.developpez.com". A copy of the license is included in the section entitled "GNU Free Documentation License".