Pattern Composite

1. Problème initial

Soit un objet X composé d’objets A et B différents où l’objet B peut lui-même contenir des objets X.
Comment appliquer une méthode sur X pour laquelle le comportement diffère selon qu’il s’agisse d’un objet A ou objet B ?

2. Solution associée

Il faut organiser nos objets en forme d’arbre afin d’avoir accès à chaque feuille pour obtenir ses spécifications propres.

3. Définition et exemple

Ce pattern étant un peu complexe à expliquer sans exemple nous allons donner un problème concret pour y appliquer, pas à pas, notre pattern.

Imaginons que nous ayons à gérer un petit magasin, lequel offre à ses clients la possibilité d'acheter :
- des pièces détachées,
- des ensembles préassemblés

Par exemple, nous vendons des équipements et vêtements sportifs :
- soit en pièces détachées (pantalon de survêtement, poids d'haltères, …),
- soit en ensemble (le survêtement complet, l'haltère avec un jeu de 5 paires de poids, …),

Pour chaque article, qu'il soit composé ou en pièces détachées, nous connaissons son prix, un nom descriptif et un code barre.

Le décor étant planté, voyons comment nous pouvons nous organiser. Tout d'abord, quelques détails fonctionnels :
- Le prix d'un ensemble est calculé suivant la méthode suivante : c'est la somme des prix de ses parties (composées ou non) moins 10% ;
- Le code barre est spécifique à chaque produit vendu qu'il soit composé ou non ;
- Le nom descriptif d'un ensemble contient à la fois un descriptif de l'ensemble et le descriptif des parties

Voyons alors comment on va pouvoir l’implémenter pour une haltère et sa barre.

Que ce soit des pièces détachées ou un ensemble, ces entités possèdent toutes des informations communes auxquelles on doit pouvoir accéder :
- Un prix,
- Une description,
- Un code barre.
On commence alors par créer une interface qui va définir des méthodes d’accès à ces éléments.



Chaque produit implémentera donc cette interface qu’il soit seul ou composé. Commençons par les produits qui sont seuls. On obtient donc pour l’haltère et la barre:



On observe que chaque produit possède des attributs communs (prix, description, code barre) mais aussi des attributs spécifiques (le poids pour l’haltère et la longueur pour la barre).

Maintenant il nous faut représenter l’ensemble de produit qui regroupe l’haltère et la barre.
Tout d’abord l’ensemble de produit est quand même un produit donc il implémentera lui aussi l’interface Produit et aura les mêmes attributs « publique » que les classes Haltère et Barre.



Maintenant il nous faut gérer le fait qu’un ensemble es un produit qui regroupe plusieurs autres produits.
Autrement dit un ensemble est un produit qui contient d’autres produits, qui contient une liste de produits simples.
On sent alors que la classe Ensemble va devoir posséder une liste des produits qui la constitue. Ne sachant pas quelle produit un Ensemble pourra contenir on indique le type Produit qui est le plus général possible.
Dans notre diagramme de classes une relation entre Ensemble et Produit symbolisera cette liste.

De plus il nous faut ajouter des méthodes pour ajouter ou supprimer un produit dans un Ensemble.

Au final on obtient :



Nous avons construit notre situation à l’aide du pattern Composite.

Néanmoins il reste encore des choses à implémenter mais cette fois-ci dans le code des classes. Le code des classes Produit, Haltere et Barre est relativement simple et ce qui nous intéresse surtout c’est le code de la classe Ensemble car il nous faut redéfinir la fonction getPrix() qui retourne le prix de l’Ensemble qui n’est rien d’autre que la somme des prix de chaque produit qui constitue l’Ensemble avec 10% en moins.

On commence par donner le code simple de la classe Ensemble avec son constructeur et les méthodes ajouter/retirer.



On voit bien que dans le constructeur de Ensemble on initialise une liste de Produit qui contiendra les produits constitutif de cet Ensemble.

Passons maintenant à la méthode getPrix() :



On voit bien qu’ici on parcourt toute la liste des produits qui constitue notre Ensemble et on appelle la méthode getPrix() de ces Produits !
On retourne finalement le prix final avec une réduction de 10%.

4. A retenir

Ce pattern est l’un des plus compliqué à comprendre car il contient des appels récursifs et des compositions imbriquées.

Pour appliquer ce pattern il faut :
  • Identifier les méthodes communes à tous les éléments (ici des produits) et les factoriser dans une interface ou une classe abstraite
  • Que chaque élément étende cette interface ou cette classe abstraite.
  • On différencie et on identifie alors deux types d’éléments :
  • Ce qu’on appelle des « feuilles » c’est-à-dire des éléments qui ne sont pas constitués d’autres éléments (la classe Haltère ou Barre)
  • Des éléments qui sont composés d’autres éléments et qu’on appelle élément « composite » (la classe Ensemble).
  • Chaque élément composite possède une liste d’élément dont il est constitué.
  • Lors d’appels récursif penser à appeler les méthodes des feuilles qu’on rencontre.
Remarque : On peut avoir une infinité de feuille et une infinité d’élément composite tant qu’ils respectent les règles énoncées ci-dessus.