IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Design pattern d'Avalon - IOC

Version 1.0

Remerciements : Stefan Bertholon

Cet article fait partie de la série :

  • Image non disponiblemonteurconstruction d'une structure complexe indépendamment de son implémentation
  • Image non disponibleiocInversion of control laissez votre environnement objet interagir avec vos objets
  • Image non disponiblesaxRelire un fichier xml avec l'API SAX
  • Image non disponibleimplémentationImplémentation, exemples

Article lu   fois.

L'auteur

Profil ProSite personnel

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

Pour commencer, il serait probablement préférable, si vous ne l'avez pas encore fait, de lire l'introduction au framework avalonintroduction au framework avalon.

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 quatre 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 relatifs à l'implémentation objet de bonne qualité.

II. 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 leur 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 ressemblerai à 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.

III. Étude

L'environnement de l'objet est suffisamment important pour que nous lui prêtions 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.

IV. 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.

IV-A. 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. Étudions l'organisation d'une petite entreprise de peinture en bâtiment dont nous considérons qu'elle a trois niveaux hiérarchiques : 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 à cœur de maîtriser les dépenses et donc de gérer ce budget de manière intelligente. À 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.

IV-B. 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 si tô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 !

IV-C. Un petit bémol

L'inversion du contrô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.

V. Exemple d'implémentation

Pour rendre encore plus concret en termes 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.

VI. 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 ».

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2013 Sébastien MERIC. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.