Un regard sur le modèle de conception composite

L'autre jour, j'écoutais Car Talk de la National Public Radio , une émission hebdomadaire populaire au cours de laquelle les appelants posent des questions sur leurs véhicules. Avant chaque pause de programme, les animateurs de l'émission demandent aux appelants de composer le 1-800-CAR-TALK, ce 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 composite: deux mots qui représentent sept chiffres. Les humains trouvent généralement plus facile de 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 modèle de conception composite,le sujet de ces modèles de conception Java Versement.

Le motif composite

Avant de plonger dans le motif composite, je dois d'abord définir des 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, etc.

Les développeurs Java ont besoin du modèle composite car nous devons souvent manipuler les composites exactement de la même manière que nous manipulons des 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 aussi 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 à implémenter, maintenir et étendre.

Dans Design Patterns , les auteurs décrivent le motif composite comme ceci:

Composez des objets en arborescences pour représenter des hiérarchies partielles. Composite permet aux clients de traiter des objets individuels et des compositions d'objets de manière uniforme.

La mise en œuvre du modèle composite est facile. Les classes composites étendent une classe de base qui représente des objets primitifs. La figure 1 montre un diagramme de classes qui illustre la structure du modèle composite.

Dans le diagramme de classes de la figure 1, j'ai utilisé les noms de classe de la discussion sur les modèles composites de Design Pattern : Componentreprésente une classe de base (ou éventuellement une interface) pour les objets primitifs, et Compositereprésente une classe composite. Par exemple, la Componentclasse peut représenter une classe de base pour les primitives graphiques, tandis que la Compositeclasse peut représenter une Drawingclasse. La Leafclasse de la figure 1 représente un objet primitif concret; par exemple, une Lineclasse ou une Textclasse. Les méthodes Operation1()et Operation2()représentent des méthodes spécifiques au domaine implémentées par les classes Componentet Composite.

La Compositeclasse gère une collection de composants. En règle générale, les Compositeméthodes sont implémentées en itérant sur cette collection et en appelant la méthode appropriée pour chacune d'entre elles Component. Par exemple, une Drawingclasse peut implémenter sa draw()méthode comme ceci:

// Cette méthode est une méthode composite public void draw () {// Itère sur les composants pour (int i = 0; i <getComponentCount (); ++ i) {// Obtient une référence au composant et appelle son dessin méthode Component component = getComponent (i); component.draw (); }}

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

La Compositeclasse étend la Componentclasse afin que vous puissiez passer un composite à une méthode qui attend un composant; par exemple, considérez la méthode suivante:

// Cette méthode est implémentée dans une classe qui n'est pas liée aux // classes Component et Composite public void repaint (Component component) {// Le composant peut être un composite, mais comme il étend // la classe Component, cette méthode n'a pas besoin // faire la distinction entre les composants et les composites component.draw (); }

La méthode précédente reçoit un composant (un composant simple ou un composite), puis elle appelle la draw()méthode de ce composant . Étant donné que la Compositeclasse s'étend Component, la repaint()méthode n'a pas besoin de faire la distinction entre les composants et les composites - elle appelle simplement la draw()méthode du composant (ou composite).

Le diagramme de classes de modèle composite de la figure 1 illustre un problème avec le modèle: vous devez faire la distinction entre les composants et les composites lorsque vous référencez a Component, et vous devez appeler une méthode spécifique au composite, telle que addComponent(). Vous remplissez généralement cette condition en ajoutant une méthode, telle que isComposite(), à la Componentclasse. Cette méthode retourne falsepour les composants et est remplacée dans la Compositeclasse à renvoyer true. De plus, vous devez également convertir la Componentréférence en une Compositeinstance, comme ceci:

... if (component.isComposite ()) {Composite composite = composant (Composite); composite.addComponent (someComponentThatCouldBeAComposite); } ...

Notez que la addComponent()méthode reçoit une Componentréférence, qui peut être un composant primitif ou un composite. Étant donné 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 autre implémentation de modèle composite.

Si vous implémentez le modèle composite de la figure 2, vous n'avez jamais à faire la distinction entre les composants et les composites, et vous n'avez pas à convertir une Componentréférence à une Compositeinstance. Ainsi, le fragment de code répertorié ci-dessus se réduit à une seule ligne:

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

Mais, si la Componentréférence dans le fragment de code précédent ne fait pas référence à a Composite, que doit-il addComponent()faire? C'est un point de discorde majeur avec l'implémentation du modèle composite de la figure 2. Étant donné que 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 Component.addComponent()méthode peut échouer silencieusement ou lever une exception. En règle générale, l'ajout d'un composant à un autre composant primitif est considéré comme une erreur, donc lever une exception est peut-être le meilleur plan d'action.

Alors, quelle implémentation de modèle 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 de modèles composites; Design Patterns préfère l'implémentation de la figure 2, car vous n'avez jamais besoin de faire la distinction entre les composants et les conteneurs, et vous n'avez jamais besoin d'effectuer une conversion. 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 modèle composite et comment vous pouvez l'implémenter, examinons un exemple de modèle composite avec le framework Apache Struts JavaServer Pages (JSP).

Le motif composite et les tuiles Struts

Le framework Apache Struts comprend une bibliothèque de balises JSP, appelée Tiles, qui vous permet de composer une page Web à partir de plusieurs JSP. Tiles est en fait une implémentation du modèle CompositeView J2EE (Java 2 Platform, Enterprise Edition), lui-même basé sur le modèle Design Patterns Composite. Avant de discuter de la pertinence du modèle composite pour la bibliothèque de balises Tiles, examinons d'abord la justification de Tiles et la manière dont vous l'utilisez. Si vous êtes déjà familiarisé avec les tuiles Struts, vous pouvez parcourir les sections suivantes et commencer à lire à "Utiliser le motif composite avec les tuiles Struts".

Remarque: Vous pouvez en savoir plus sur le modèle J2EE CompositeView dans mon article «Composants d'application Web simplifiés avec la vue composite» ( JavaWorld, décembre 2001).

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

Les sites Web incluent souvent plusieurs pages Web avec des dispositions identiques, telles que la disposition de la barre latérale / de l'en-tête / du contenu / du pied de page de la figure 3. Struts Tiles vous permet de réutiliser à la fois le contenu et la mise en page sur plusieurs pages Web. Avant de discuter de cette réutilisation, voyons comment la mise en page de la figure 3 est traditionnellement implémentée avec HTML uniquement.

Mettre en œuvre des mises en page complexes à la main

L'exemple 1 montre comment vous pouvez implémenter la page Web de la figure 3 avec HTML:

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

    Implémentation manuelle de mises en page complexes <% - Un tableau présente tout le contenu de cette page -%>
   
Liens

Accueil

Des produits

Téléchargements

papiers blanc

Nous contacter

Bienvenue chez Sabreware, Inc.
Le contenu spécifique à la page va ici

Merci d'être passé!

La JSP précédente présente deux inconvénients majeurs: Premièrement, le contenu de la page est intégré dans la JSP, vous ne pouvez donc en réutiliser aucune, 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 la réutiliser même si de nombreuses autres pages Web du même site Web utilisent la même mise en page. Nous pouvons utiliser l' action pour remédier au premier inconvénient, comme je l'expliquerai ensuite.

Implémenter des mises en page complexes avec JSP comprend

L'exemple 2 montre une implémentation de la page Web de la figure 3 qui utilise :