L’autre jour, j’écoutais l’émission Car Talk de National Public Radio, une émission hebdomadaire populaire au cours de laquelle les appelants posent des questions sur leurs véhicules. Avant chaque pause du programme, les animateurs de l’émission demandent aux appelants de composer le 1-800-CAR-TALK, qui correspond au 1-800-227-8255. Bien sûr, le premier s’avère beaucoup plus facile à retenir que le second, en partie parce que les mots « CAR TALK » sont un composé : deux mots qui représentent sept chiffres. Les êtres humains ont généralement plus de facilité à traiter les composites que leurs composants individuels. De même, lorsque vous développez un logiciel orienté objet, il est souvent pratique de manipuler des composites comme vous manipulez des composants individuels. Cette prémisse représente le principe fondamental du patron de conception Composite, le sujet de ce versement sur les patrons de conception Java.

Le patron Composite

Avant de nous plonger dans le patron Composite, je dois d’abord définir les objets composites : des objets qui contiennent d’autres objets ; par exemple, un dessin peut être composé de primitives graphiques, telles que des lignes, des cercles, des rectangles, du texte, et ainsi de suite.

Les développeurs Java ont besoin du pattern Composite parce que nous devons souvent manipuler les composites exactement de la même manière que nous manipulons les objets primitifs. Par exemple, les primitives graphiques telles que les lignes ou le texte doivent être dessinées, déplacées et redimensionnées. Mais nous voulons également effectuer la même opération sur les composites, tels que les dessins, qui sont composés de ces primitives. Idéalement, nous aimerions effectuer des opérations sur les objets primitifs et les composites exactement de la même manière, sans faire de distinction entre les deux. Si nous devons faire la distinction entre les objets primitifs et les composites pour effectuer les mêmes opérations sur ces deux types d’objets, notre code deviendrait plus complexe et plus difficile à mettre en œuvre, à maintenir et à étendre.

Dans Design Patterns, les auteurs décrivent le patron Composite comme suit :

Composer des objets en structures arborescentes pour représenter des hiérarchies partie-entière. Composite permet aux clients de traiter les objets individuels et les compositions d’objets de manière uniforme.

La mise en œuvre du motif Composite est facile. Les classes composites étendent une classe de base qui représente les objets primitifs. La figure 1 montre un diagramme de classes qui illustre la structure du patron Composite.

Figure 1. Un diagramme de classe du motif Composite

Dans le diagramme de classe de la figure 1, j’ai utilisé les noms de classe de la discussion sur le motif Composite de Design Pattern : Component représente une classe de base (ou éventuellement une interface) pour les objets primitifs, et Composite représente une classe composite. Par exemple, la classe Component pourrait représenter une classe de base pour les primitives graphiques, tandis que la classe Composite pourrait représenter une classe Drawing. La classe Leaf de la figure 1 représente un objet primitif concret ; par exemple, une classe Line ou une classe Text. Les méthodes Operation1() et Operation2() représentent des méthodes spécifiques au domaine mises en œuvre par les classes Component et Composite.

La classe Composite gère une collection de composants. Typiquement, les méthodes Composite sont implémentées en itérant sur cette collection et en invoquant la méthode appropriée pour chaque Component de la collection. Par exemple, une classe Drawing peut implémenter sa méthode draw() comme suit :

Pour chaque méthode implémentée dans la classe Component, la classe Composite implémente une méthode avec la même signature qui itère sur les composants du composite, comme illustré par la méthode draw() listée ci-dessus.

La classe Composite étend la classe Component, de sorte que vous pouvez passer un composite à une méthode qui attend un composant ; par exemple, considérez la méthode suivante :

// This method is implemented in a class that's unrelated to the// Component and Composite classespublic void repaint(Component component) { // The component can be a composite, but since it extends // the Component class, this method need not // distinguish between components and composites component.draw();}

On passe à la méthode précédente un composant – soit un composant simple, soit un composite – puis elle invoque la méthode draw() de ce composant. Parce que la classe Composite étend Component, la méthode repaint() n’a pas besoin de faire la distinction entre les composants et les composites – elle invoque simplement la méthode draw() pour le composant (ou le composite).

Le diagramme de classe du motif Composite de la figure 1 illustre un problème avec le motif : vous devez faire la distinction entre les composants et les composites lorsque vous faites référence à un Component, et vous devez invoquer une méthode spécifique au composite, comme addComponent(). Vous remplissez généralement cette exigence en ajoutant une méthode, telle que isComposite(), à la classe Component. Cette méthode renvoie false pour les composants et est surchargée dans la classe Composite pour renvoyer true. De plus, vous devez également convertir la référence Component en une instance Composite, comme ceci :

Notez que la méthode addComponent() reçoit une référence Component, qui peut être soit un composant primitif, soit un composite. Parce que ce composant peut être un composite, vous pouvez composer des composants dans une structure arborescente, comme indiqué par la citation susmentionnée de Design Patterns.

La figure 2 montre une implémentation alternative du motif Composite.

Figure 2. Un diagramme de classe alternatif du motif Composite

Si vous mettez en œuvre le motif Composite de la figure 2, vous n’avez jamais à faire la distinction entre les composants et les composites, et vous n’avez pas à couler une référence Component vers une instance Composite. Ainsi, le fragment de code ci-dessus se réduit à une seule ligne:

...component.addComponent(someComponentThatCouldBeAComposite);...

Mais, si la référence Component dans le fragment de code précédent ne fait pas référence à une Composite, que doit faire la addComponent() ? C’est un point de discorde majeur avec l’implémentation du motif Composite de la figure 2. Comme les composants primitifs ne contiennent pas d’autres composants, l’ajout d’un composant à un autre composant n’a aucun sens, de sorte que la méthode Component.addComponent() peut soit échouer silencieusement, soit lever une exception. Typiquement, ajouter un composant à un autre composant primitif est considéré comme une erreur, donc lancer une exception est peut-être la meilleure ligne de conduite.

Alors, quelle implémentation du motif Composite – celle de la figure 1 ou celle de la figure 2 fonctionne le mieux ? C’est toujours un sujet de grand débat parmi les implémenteurs du patron Composite ; Design Patterns préfère l’implémentation de la Figure 2 parce que vous n’avez jamais besoin de faire la distinction entre les composants et les conteneurs, et vous n’avez jamais besoin d’effectuer un cast. Personnellement, je préfère l’implémentation de la Figure 1, car j’ai une forte aversion pour l’implémentation de méthodes dans une classe qui n’ont pas de sens pour ce type d’objet.

Maintenant que vous comprenez le motif Composite et comment vous pouvez l’implémenter, examinons un exemple de motif Composite avec le framework Apache Struts JavaServer Pages (JSP).

Le motif Composite et Struts Tiles

Le framework Apache Struts inclut une bibliothèque de balises JSP, connue sous le nom de Tiles, qui vous permet de composer une page Web à partir de plusieurs JSP. Tiles est en fait une implémentation du motif CompositeView de J2EE (Java 2 Platform, Enterprise Edition), lui-même basé sur le motif Composite de Design Patterns. Avant de discuter de la pertinence du modèle Composite pour la bibliothèque de balises Tiles, examinons d’abord la raison d’être de Tiles et son utilisation. Si vous êtes déjà familiarisé avec Struts Tiles, vous pouvez survoler les sections suivantes et commencer votre lecture à « Utiliser le motif Composite avec Struts Tiles. »

Note : Vous pouvez en savoir plus sur le motif J2EE CompositeView dans mon article « Web Application Components Made Easy with Composite View » (JavaWorld, décembre 2001).

Les concepteurs construisent souvent des pages Web avec un ensemble de régions discrètes ; par exemple, la page Web de la figure 3 comprend une barre latérale, un en-tête, une région de contenu et un pied de page.

Figure 3. Le motif composite et les tuiles Struts. Cliquez sur la vignette pour afficher l’image en taille réelle.

Les sites Web comprennent souvent plusieurs pages Web avec des dispositions identiques, comme la disposition barre latérale/en-tête/contenu/pied de page de la figure 3. Struts Tiles vous permet de réutiliser à la fois le contenu et la mise en page dans plusieurs pages Web. Avant de discuter de cette réutilisation, voyons comment la mise en page de la Figure 3 est traditionnellement mise en œuvre avec HTML seul.

Mise en œuvre manuelle de mises en page complexes

L’exemple 1 montre comment vous pouvez mettre en œuvre la page Web de la Figure 3 avec HTML:

Exemple 1. Une mise en page complexe implémentée à la main

La JSP précédente présente deux inconvénients majeurs : Premièrement, le contenu de la page est intégré à la JSP, vous ne pouvez donc en réutiliser aucune partie, même si la barre latérale, l’en-tête et le pied de page sont susceptibles d’être les mêmes sur de nombreuses pages Web. Deuxièmement, la mise en page de la page est également intégrée dans cette JSP, vous ne pouvez donc pas non plus la réutiliser, même si de nombreuses autres pages Web du même site utilisent la même mise en page. Nous pouvons utiliser l’action <jsp:include> pour remédier au premier inconvénient, comme je l’explique ci-après.

Mise en œuvre de mises en page complexes avec JSP includes

L’exemple 2 montre une mise en œuvre de la page Web de la figure 3 qui utilise <jsp:include>:

Exemple 2. Une mise en page complexe implémentée avec JSP inclut

La JSP précédente inclut le contenu d’autres JSP avec <jsp:include>. Comme j’ai encapsulé ce contenu dans des JSP distinctes, vous pouvez le réutiliser pour d’autres pages Web:

Exemple 3. sidebar.jsp

Pour être complet, j’ai listé ci-dessous les JSP incluses par la JSP précédente:

Exemple 4. header.jsp

<font size='6'>Welcome to Sabreware, Inc.</font><hr>

Exemple 5. content.jsp

<font size='4'>Page-specific content goes here</font>

Exemple 6. footer.jsp

<hr>Thanks for stopping by!

Même si la JSP de l’exemple 2 utilise <jsp:include> pour réutiliser le contenu, vous ne pouvez pas réutiliser la mise en page de la page car elle est codée en dur dans cette JSP. Struts Tiles vous permet de réutiliser à la fois le contenu et la mise en page, comme illustré dans la section suivante.

Implémenter des mises en page complexes avec Struts Tiles

L’exemple 7 montre la page Web de la figure 3 implémentée avec Struts Tiles :

Exemple 7. Utiliser Struts Tiles pour encapsuler la mise en page

Le JSP précédent utilise la balise <tiles:insert> pour créer le JSP de la figure 3. Cette JSP est définie par une définition de tiles nommée sidebar-header-footer-definition. Cette définition réside dans le fichier de configuration Tiles, qui dans ce cas est WEB-INF/tiles-defs.xml, listé dans l’exemple 8:

Exemple 8. WEB-INF/tiles-defs.xml

La définition Tiles précédente spécifie la mise en page, encapsulée dans header-footer-sidebar-layout.jsp, et le contenu de la page, encapsulé dans sidebar.jsp, header.jsp, content.jsp et footer.jsp, comme indiqué dans les exemples 3-6. L’exemple 9 répertorie le JSP qui définit la mise en pageheader-footer-sidebar-layout.jsp:

Exemple 9. header-footer-sidebar-layout.jsp

Le JSP précédent encapsule la mise en page et insère le contenu selon les valeurs spécifiées pour les régions sidebar, editor, content et footer dans le fichier de définition des Tiles, ce qui facilite la réutilisation à la fois du contenu et de la mise en page. Par exemple, vous pouvez définir une autre définition Tiles avec la même mise en page, la même barre latérale, le même éditeur et le même pied de page, mais un contenu différent :

Pour créer la JSP définie par la définition tiles a-different-sidebar-header-footer-definition, vous utilisez la balise <tiles:insert>, comme ceci :

Grâce à Struts Tiles, vous pouvez réutiliser à la fois le contenu et la mise en page, ce qui s’avère précieux pour les sites Web comportant de nombreuses JSP qui partagent la mise en page et une partie du contenu. Mais si vous regardez attentivement le code des exemples 7 à 9, vous remarquerez que la mise en page de la région de la barre latérale est codée en dur dans sidebar.jsp, qui est répertorié dans l’exemple 3. Cela signifie que vous ne pouvez pas réutiliser cette disposition. Heureusement, la bibliothèque de balises Tiles met en œuvre le modèle Composite, qui nous permet de spécifier une définition de tuiles – au lieu d’un JSP – pour une région. Dans la section suivante, j’explique comment utiliser cette implémentation du motif Composite.

Utiliser le motif Composite avec Struts Tiles

Struts Tiles implémente le motif Composite, où la classe Component est représentée par des JSP et la classe Composite est représentée par une définition de Tiles. Cette implémentation vous permet de spécifier soit une JSP (un composant) soit une définition Tiles (un composite) comme contenu de la région d’une JSP. L’exemple 10 illustre cette fonctionnalité :

Exemple 10. WEB-INF/tiles-defs.xml : Utiliser le motif Composite

Le fichier de configuration Tiles précédent définit deux définitions Tiles : sidebar-definition et sidebar-header-footer-definition. Le sidebar-definition est spécifié comme la valeur de la région de la barre latérale dans le sidebar-header-footer-definition. Vous pouvez la spécifier comme telle parce que Tiles met en œuvre le modèle Composite en laissant Tiles spécifier une définition (un Composite qui est une collection de JSP) là où vous spécifieriez normalement un seul JSP (qui est un Component).

La disposition de la barre latérale est encapsulée dans le sidebar-layout.jsp de l’exemple 11:

Exemple 11. sidebar-layout.jsp

L’exemple 12 liste flags.jsp, spécifié comme le contenu de la région top de la barre latérale, et l’exemple 13 liste sidebar-links.jsp, spécifié comme la région bottom de la barre latérale:

Exemple 12. flags.jsp

Exemple 13. sidebar-links.jsp

Maintenant, le sidebar-definition peut définir d’autres régions avec un composant supérieur et inférieur, bien que vous devriez probablement renommer cette définition en quelque chose de plus générique comme top-bottom-definition.

Ce sont tous des composites ces jours-ci

Le motif Composite est populaire avec les frameworks de présentation, tels que Swing et Struts, parce qu’il vous permet d’imbriquer les conteneurs en traitant les composants et leurs conteneurs exactement de la même manière. Struts Tiles utilise le motif Composite pour spécifier une simple JSP ou une définition Tiles – qui est une collection de JSP – comme contenu d’une tuile. C’est une capacité puissante qui facilite la gestion de grands sites Web avec différentes mises en page.

La section « Devoir de la dernière fois » ci-dessous développe la discussion de cet article en internationalisant l’application précédente avec une action Struts et la bibliothèque de balises standard JSP (JSTL).

Du devoir

Discutez de la façon dont Swing met en œuvre le motif composite avec les classes Component et Container.

Document de travail de la dernière fois

Votre dernier devoir vous demandait de télécharger Struts à partir de http://jakarta.apache.org/struts/index.html et d’implémenter votre propre classe d’action Struts.

Pour ce devoir, j’ai décidé d’implémenter une action Struts en conjonction avec la bibliothèque de balises standard JSP (JSTL) pour internationaliser l’application Web de l’exemple 1. Bien que Struts fournisse l’infrastructure nécessaire pour internationaliser vos applications Web, vous devriez utiliser JSTL pour cette tâche, car JSTL est un standard. À un moment donné, les capacités d’internationalisation de Struts seront probablement dépréciées ou intégrées à JSTL.

Après avoir internationalisé l’application Web de l’exemple 1 avec une action Struts et JSTL, j’ai localisé cette application pour le chinois. La figure H1 illustre le résultat.

Note : Je ne connais pas un seul mot de chinois, et encore moins la façon d’écrire cette langue, donc le chinois de la figure H1 est fabriqué à partir de chaînes Unicode arbitraires. Mais il a l’air cool, malgré tout.

Figure H1. Internationalisation avec une action Struts. Cliquez sur la vignette pour afficher l’image en taille réelle.

Notez que j’ai spécifié les attributs href dans les flags.jsp de l’exemple 12 comme des chaînes vides, donc lorsque vous cliquez sur les drapeaux, le conteneur de servlet recharge la JSP actuelle. Par conséquent, la première étape vers l’internationalisation de l’application Web consiste à spécifier une URL pour ces attributs, comme indiqué dans l’exemple H1:

Exemple H1. flags.jsp

L’URL pour chaque attribut href est la même : flags.do. Deux paramètres de requête sont accolés à cette URL : un pour la locale correspondant au drapeau et un autre qui représente le chemin de la JSP actuelle. Vous obtenez ce dernier paramètre de requête en invoquant la méthode Http.ServletRequest.getServletPath().

Dans le descripteur de déploiement de l’application, j’ai mappé toutes les URL se terminant par .do vers le servlet d’action Struts, comme ceci:

Exemple H2. WEB-INF/web.xml (Extrait)

Note : Voir mon article « Take Command of Your Software » (JavaWorld, juin 2002) pour plus d’informations sur Struts et le servlet d’action Struts.

Puis, j’ai mappé le chemin /flags à l’action Struts actions.FlagAction dans le fichier de configuration Struts, listé dans l’exemple H3:

Exemple H3. WEB-INF/struts-config.xml

En raison de ce mappage, l’URL flags.do fait en sorte que le servlet d’action Struts invoque la méthode actions.FlagAction.execute() ; par conséquent, le fait de cliquer sur un drapeau invoquera la méthode actions.FlagAction.execute(). L’exemple H4 répertorie la classe actions.FlagAction:

Exemple H4. WEB-INF/classes/actions/FlagAction.java

La méthode actions.FlagAction.execute() obtient une référence au paramètre de requête locale et transmet cette valeur à la méthode set() de la classe JSTL Config. Cette méthode stocke la chaîne représentant la locale dans le paramètre de configuration FMT_LOCALE, que JSTL utilise pour localiser le texte et formater les nombres, les devises, les pourcentages et les dates.

Maintenant que j’ai spécifié une locale pour les actions d’internationalisation de JSTL, je spécifie ensuite un bundle de ressources dans le descripteur de déploiement de l’application, dont un extrait est répertorié dans l’exemple H5:

Exemple H5. WEB-INF/web.xml (Extrait)

Le nom de base du bundle de ressources resources est spécifié pour le paramètre javax.servlet.jsp.jstl.fmt.localizationContext d’initialisation du contexte. Cela signifie que JSTL recherchera un fichier nommé resources_en.properties lorsque la locale est définie sur l’anglais britannique (en-GB) et resources_zh.properties lorsque la locale est définie sur le chinois (zh-ZH).

Maintenant que j’ai spécifié un faisceau de ressources et une locale pour les actions d’internationalisation de JSTL, je modifie ensuite les fichiers JSP pour utiliser ces actions, comme le montrent les exemples H6-H9 :

Exemple H6. sidebar-links.jsp

Exemple H7. header.jsp

Exemple H8. content.jsp

Exemple H9. footer.jsp

Les JSP des exemples H6-H9 utilisent l’action JSTL <fmt:message> pour extraire les chaînes Unicode du bundle de ressources spécifié dans le descripteur de déploiement. Les exemples H10 et H11 listent les bundles de ressources pour l’anglais et le chinois, respectivement :

Exemple H10. WEB-INF/classes/ressources_fr.properties

Exemple H11. WEB-INF/classes/resources_zh.properties

Email

Dans un courriel qui m’a été adressé, Joseph Friedman écrit :

Dans « Decorate Your Java Code » (JavaWorld, décembre 2001), vous écrivez :

« L’objet englobant – connu sous le nom de décorateur – se conforme à l’interface de l’objet qu’il englobe, permettant au décorateur d’être utilisé comme s’il était une instance de l’objet qu’il englobe. »

Cependant, l’exemple de code décore le FileReader avec un LineNumberReader et appelle readLine(), qui n’est pas dans l’interface du FileReader ; vous n’utilisez donc pas le LineNumberReader de manière transparente.

Les deux FileReader et LineNumberReader sont conformes à l’interface définie par la classe Reader, qu’ils étendent tous les deux. L’idée est que vous pouvez passer le décorateur à toute méthode qui attend une référence à une Reader. Ces méthodes restent béatement ignorantes des capacités spéciales de ce décorateur – dans ce cas, la capacité de lire des fichiers et de produire des numéros de ligne. Cependant, si vous connaissez ces capacités spéciales, vous pouvez en tirer parti.

Le fait que le décorateur possède (une ou plusieurs) méthodes dont l’objet décoré est dépourvu ne viole en rien l’intention du pattern Decorator ; en fait, c’est la caractéristique centrale de ce pattern : ajouter des fonctionnalités à l’exécution à un objet en décorant des objets de manière récursive.

Si vous regardez Design Patterns page 173, vous verrez ceci :

Les sous-classes de Decorator sont libres d’ajouter des opérations pour des fonctionnalités spécifiques. Par exemple, l’opération ScrollTo de ScrollDecorator permet aux autres objets de faire défiler l’interface *si* ils savent qu’il se trouve qu’il y a un objet ScrollDecorator dans l’interface.

DavidGeary est l’auteur de Core JSTL Mastering the JSP Standard TagLibrary, qui sera publié cet automne parPrentice-Hall et Sun Microsystems Press ; Advanced JavaServer Pages (PrenticeHall, 2001 ; ISBN : 0130307041) ; et la série Graphic Java (Sun MicrosystemsPress). Depuis 18 ans, David développe des logiciels orientés objet avec de nombreux langages orientés objet. Depuis la publication du livre GOFDesign Patterns en 1994, David a été un partisan actif des modèles de conception, et a utilisé et implémenté des modèles de conception dans Smalltalk, C++ et Java. En 1997, David a commencé à travailler à plein temps en tant qu’auteur, conférencier et consultant occasionnel. David est membre des groupes d’experts qui définissent la bibliothèque standard de balises personnalisées JSP et JavaServer Faces, et il est un contributeur du cadre JSP Apache Struts.

Learn more about this topic

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée.