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 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 :

 
Sélectionnez
<!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 :

 
Sélectionnez
<?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 inteface.

Voici le diagramme de classe UML de la structure du monteur :

Image non disponible

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.

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 :

Image non disponible

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.