|
|
|
|
Le monteur - Uml design pattern |
|
|
 |
|
|
|
|
|
5 Octobre 2003 |
|
|
Version 1.0 |
|
|
Par Sébastien MERIC |
|
|
Remerciements : Stefan Bertholon |
|
Synopsis
Dissocie le processus de construction d'un objet complexe de la structure de
représentation de cet objet.
Exploration
Construire une arborescence plus ou moins compliquée d'objets est somme
toute une fonctionnalité assez courante de nos développements
et l'un des cas d'utilisation les plus courants se trouve être la construction
d'une arborescence contextuelle d'une application. En effet un contexte applicatif
est rarement limité au simple alignement de constantes représentées
par une paire nom/valeur. C'est évidemment le plus simple à mettre
en oeuvre (en particulier en java avec l'aide d'outils comme les ResourceBundle)
mais très rapidement limitatif. Bien entendu, l'avénement des
normalisations XML permet aujourd'hui d'imaginer construire un fichier de configuration
plus contextuel mais le rapatriement des informations structurées en
arborescence ne simplifie pas l'implémentation d'un constructeur efficace
de celle-ci sous sa forme objet.
Pour autant, la même arborescence XML peut avoir plusieurs représentations
Objet différentes dépendantes de l'application dans laquelle elle
est chargée, ou tout au moins dans le module dans lequel elle le sera.
Il serait évidemment dommage de devoir réécrire l'algorithme
de construction de l'arborescence sous le seul prétexe que notre représentation
objet est elle-même différente. D'autant que l'on rencontrera très
souvent le cas d'une représentation objet partiellement différente.
C'est à cette fin qu'a été suggéré le design
pattern du monteur que je vous propose de découvir dans ce tutoriel.
Nous allons donc nous formaliser un petit contexte applicatif arborescent et
voir comment nous pouvons assez simplement grâce au design pattern "monteur"
construire celle-ci en suivant l'API SAX de parseurs de fichiers XML.
Pour illustrer mon propos, je partirai sur l'article
concernant le design pattern composite. En effet, le but est de donner le
code spécifique au monteur et non de fournir l'implémentation
des composants que l'on construit. Le composite nous fournie en effet une structure
suffisemment "complexe" pour fournir une bonne idée de la puissance
du monteur. Un petit rappel s'impose toutefois même si je vous conseille
vivement de lire l'article sur le design pattern composite. Nous avons géré
dans cet article un back office permettant à un vendeur de composer ses
offres à partir de produits simples. Tous les produits présents
en magasin devaient être dotés d'un prix, d'un code barre et d'un
libellé. Ils pouvaient éventuellement contenir un complément
d'information quand ceux-ci n'étaient pas composés. Le prix d'un
élément composé s'effectue par un calcul défini
fonctionnellement et nous désintéresse dans cet article.
Le fait que nous ayons un exemple d'arborecence plus ou moins complexe ne suffit
évidemment pas à utiliser le pattern monteur qui nous proposent
de permettre plusieurs implémentations de la même structure tout
en gardant intact l'algorithme de construction de la structure. Et pourtant,
nous oublions trop souvent que nos développements sont sensés
évoluer, que d'une version à l'autre certains de nos objets seront
restés inchangés tandis que d'autres auront évolués.
Pourquoi s'en soucier puisque les objets des versions futures seront aptes à
reproduire les comportements des précédents ? Rien n'est moins
sûr que cette assertion, pensez que pour des problèmes de compatibilité
vous aurez besoin d'être capable de sérialiser et désérialiser
votre arborescence sous diverses formes équivalentes à des versions,
à des formats de sérialisation, etc.
Cette notion restant probablement abstraite, je me permet quand même
de donner un autre exemple possible de l'utilisation du monteur, que je ne développerai
pas, je vous laisse le soin d'en faire un exercice si vous le désirez.
Imaginons que nous voulions fabriquer une interface graphique "skinable".
Basée sur un context unique, il faudra être capable de fournir
des objets spécifiques pour chaque présentation. Un autre exemple
du même genre serait de fournir un algorithme de construction d'une interface
graphique pour les diverses bibliothèques graphiques fournies en java
(AWT, SWING, SWT, ...).
Une bonne chose à faire maintenant sera, puisque nous partons de l'idée
d'une persistance sous la forme d'un fichier XML, de définir la DTD qui
nous permettra de saisir manuellement un arbre de produits. Tout d'abord, il
faudra définir les produits simples, qui font partie du catalogue, et
qui servent à la construction de produits plus élaborés.
Ensuite, il nous faudra être capable de construire les produits composés,
voici la DTD que j'imagine donc dans notre cas :
~/Sites/smeric/java/uml/monteur/catalogue.dtd.html
<!ELEMENT catalog ((element | composition)+)>
<!ELEMENT element (descriptif, prix, propriete*)>
<!ATTLIST element type CDATA #REQUIRED>
<!ELEMENT composition (descriptif, (element|reference|composition)+)>
<!ELEMENT descriptif EMPTY>
<!ATTLIST descriptif libelle CDATA #REQUIRED>
<!ATTLIST descriptif code-barre CDATA #REQUIRED>
<!ELEMENT propriete EMPTY>
<!ATTLIST propriete nom CDATA #REQUIRED>
<!ATTLIST propriete valeur CDATA #REQUIRED>
<!ELEMENT prix (#PCDATA)>
<!ELEMENT reference EMPTY>
<!ATTLIST reference code-barre CDATA #REQUIRED>
Voici un exemple de fichier XML que nous pourrions alors trouver conforme à
cette DTD :
~/Sites/smeric/java/uml/monteur/catalogue.xml.html
<?xml version="1.0"?>
<!DOCTYPE catalog SYSTEM "catalogue.dtd">
<catalog>
<element type="barre">
<descriptif libelle="barre 2m 1,5Kg" code-barre="0001294"/>
<prix>50</prix>
<propriete nom="poids" valeur="1,5kg"/>
<propriete nom="longueur" valeur="2m"/>
<propriete nom="diametre" valeur="3,5cm"/>
</element>
<element type="barre">
<descriptif libelle="barre 2m 1Kg" code-barre="0001293"/>
<prix>30</prix>
<propriete nom="poids" valeur="1kg"/>
<propriete nom="longueur" valeur="2m"/>
<propriete nom="diametre" valeur="3cm"/>
</element>
<element type="poids">
<descriptif libelle="poids 0,5Kg" code-barre="0002287"/>
<prix>70</prix>
<propriete nom="poids" valeur="0,5kg"/>
<propriete nom="diametre" valeur="10cm"/>
</element>
<element type="poids">
<descriptif libelle="poids 1Kg" code-barre="0002288"/>
<prix>80</prix>
<propriete nom="poids" valeur="1kg"/>
<propriete nom="diametre" valeur="10cm"/>
</element>
<element type="poids">
<descriptif libelle="poids 2Kg" code-barre="0002289"/>
<prix>90</prix>
<propriete nom="poids" valeur="2kg"/>
<propriete nom="diametre" valeur="15cm"/>
</element>
<element type="poids">
<descriptif libelle="poids 3Kg" code-barre="0002290"/>
<prix>95</prix>
<propriete nom="poids" valeur="3kg"/>
<propriete nom="diametre" valeur="20cm"/>
</element>
<composition>
<descriptif libelle="haltere legere" code-barre="0003098"/>
<reference code-barre=""/>
<reference code-barre=""/>
<reference code-barre=""/>
</composition>
<composition>
<descriptif libelle="haltere familiale" code-barre="0011563"/>
<reference code-barre="0003098"/>
<composition>
<descriptif libelle="haltere lourde" code-barre="0002292"/>
<reference code-barre=""/>
<reference code-barre=""/>
<element type="poids">
<descriptif libelle="poids 4Kg" code-barre="0002291"/>
<prix>100</prix>
<propriete nom="poids" valeur="4kg"/>
<propriete nom="diametre" valeur="25cm"/>
</element>
</composition>
</composition>
</catalog>
Structure
Nous en avons fini avec l'exploration de cas concrets et possibles du pattern
monteur, passons donc de nouveau à un peu plus de théorie et étudions
à présent la structure générique d'un monteur telle
qu'elle a été proposée par le GOF. Il nous faut donc un
objet capable de construire la structure, c'est-à-dire une classe contenant
l'algorithme de construction. Ensuite, une interface qui nous fournit les méthodes
que doivent implémenter les classes de délégations faisant
effectivement de la construction, et bien entendu, il nous faut au moins une
implémentation de cette inteface.
Voici le diagramme de classe UML de la structure du monteur :

Implémentation
Reprenons l'exemple et essayons de détailler à partir du diagramme
d'états UML ce qu'il faut présenter dans notre diagramme de classes.
Voyons par étapes, le travail à effectuer :
- Pour les besoins XML, nous avons déjà défini une DTD,
ce qui n'est requis que dans ce cas évidemment.
- Définir l'interface de construction.
- Ecrire le monteur et l'algorithme de construction conformément à
notre DTD et aux besoins particuliers de notre application.
- Implémenter le ou les constructeurs réels.
Voici le diagramme de classes UML qu'on en déduit :

Pour ce qui est de l'implémentation en java du diagramme précédent,
je tiens avant de la présenter à vous fournir un complément
d'information. Il s'agit d'une part de vous faire découvrir un autre
design pattern l'IOC (Inversion Of Control ou contrôle inversé
en français) et d'autre part de vous faire une présentation de
l'implémentation d'un parser SAX en java. Je déroge donc à
la règle qui voulait que je vous présente l'implémentation
java en même temps que le reste du pattern. D'ici peu cependant, je vous
fournirai trois articles : le pattern IOC,
comment parser un fichier XML avec l'API SAX
en java et enfin une page qui présentera le code
implémenté de ces trois articles.
|