I. 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 œuvre (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étexte 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écouvris 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 fournit en effet une structure suffisamment « 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'arborescence plus ou moins complexe ne suffit évidemment pas à utiliser le pattern monteur qui nous propose 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 censés évoluer, que d'une version à l'autre certains de nos objets seront restés inchangés tandis que d'autres auront évolué. 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 permets 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 contexte 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 capables de construire les produits composés, voici la DTD que j'imagine donc dans notre cas :
<!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 :
<?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>
II. 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 interface.
Voici le diagramme de classe UML de la structure du monteur :
III. 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.
Écrire 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 SAXen java et enfin une page qui présentera le code implémenté de ces trois articles.