I. Exploration

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 ou même des ensembles plus complets fait de plusieurs ensembles pré assemblés. Par exemple, nous vendons des équipements et vêtements sportifs, soit en pièces détachées (chaussures, 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, …), soit enfin des packages complets (haltère + survêtement + serviette). 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

Nous nous pencherons ici sur le back office de notre magasin, c'est-à-dire sur l'aide à la composition de produits pour le vendeur. Chaque produit a un ensemble de propriétés qui lui sont propres, par exemple le poids d'haltère a un poids, le rameur un encombrement, etc. ce qui explique que je leur fournisse une description (classe) à chacun.

Il n'en reste pas moins que l'interface générique servira à la composition et que nous préférons donc au niveau de ce back office, disposer d'un moyen unique de composition, et de manipulation, c'est là que le pattern intervient.

II. Structure

Notre structure doit donc disposer d'un point d'entrée générique : Le produit, de feuilles pour notre arborescence : Les pièces détachées et d'objets composites.

Image non disponible

III. Implémentation

Reprenons donc notre exemple mais simplifié, je vais disposer de deux classes : barre (d'haltère) et poids et je montrerai une composition : l'haltère complète (poids + barre).

Source de l'interface Produit

 
Sélectionnez
/*
 * Produit.java
 *
 * Created on 12 mars 2003, 18:35
 */

package com.developpez.composite;

/**
 *
 * @author  smeric
 */
public interface Produit {

    /**
     * Fournit le prix du produit.
     * Peut être calculé.
     */
    public float getPrix();
    /**
     * Code barre unique d'un produit.
     */
    public String getCodeBarre();
    /**
     * Descriptif du produit. Peut être le résultat d'une accumulation de 
     * descriptifs si le produit est composé
     */
    public String getDescriptif();

}

Source de Barre

 
Sélectionnez
/*
 * halterre.java
 *
 * Created on 12 mars 2003, 18:56
 */

package com.developpez.composite;

/**
 *
 * @author  smeric
 */
public class Barre implements Produit {

    /** Creates a new instance of haltere */
    public Barre() {
    }

    /** Code barre unique d'un produit.
     */
    public String getCodeBarre() {
        return codeBarre;
    }

    /** Descriptif du produit. Peut être le résultat d'une accumulation de
     * descriptifs si le produit est composé
     */
    public String getDescriptif() {
        return descriptif;
    }

    /** Fournit le prix du produit.
     * Peut être calculé.
     */
    public float getPrix() {
        return prix;
    }

    /** Getter for property longueur.
     * @return Value of property longueur.
     */
    public float getLongueur() {
        return this.longueur;
    }

    /** Setter for property longueur.
     * @param longueur New value of property longueur.
     */
    public void setLongueur(float longueur) {
        this.longueur = longueur;
    }

    private float poids = 0;
    private float prix = 0;
    private String codeBarre;
    private String descriptif;

    /** Holds value of property longueur. */
    private float longueur;

}

Source de Poids

 
Sélectionnez
/*
 * Poids.java
 *
 * Created on 12 mars 2003, 18:39
 */

package com.developpez.composite;

/**
 *
 * @author  smeric
 */
public class Poids implements Produit {

    /** Creates a new instance of Poids */
    public Poids() {
    }

    public float getPoids() {
        return poids;
    }

    /** Code barre unique d'un produit.
     */
    public String getCodeBarre() {
        return codeBarre;
    }

    /** Descriptif du produit. Peut être le résultat d'une accumulation de
     * descriptifs si le produit est composé
     */
    public String getDescriptif() {
        return descriptif;
    }

    /** Fournit le prix du produit.
     * Peut être calculé.
     */
    public float getPrix() {
        return prix;
    }

    private float poids = 0;
    private float prix  = 0;
    private String codeBarre;
    private String descriptif;
}

Source de ProduitException

 
Sélectionnez
/*
 * ProduitException.java
 *
 * Created on 13 mars 2003, 10:59
 */

package com.developpez.composite;

import java.lang.RuntimeException;

/**
 *
 * @author  smeric
 */
public class ProduitException extends RuntimeException {
    public ProduitException() {
        super();
    }
    public ProduitException(String msg) {
        super(msg);
    }
    public ProduitException(Exception e) {
        super(e);
    }
}

Source de ProduitComposite

 
Sélectionnez
/*
 * ProduitComposite.java
 *
 * Created on 12 mars 2003, 19:01
 */

package com.developpez.composite;

import java.util.*;

/**
 * Classe d'implémentation d'un produit composite pour notre magasin.
 * @author  smeric
 */
public class ProduitComposite implements Produit {

    /** Creates a new instance of ProduitComposite */
    public ProduitComposite() {
        children = new ArrayList();
    }

    /** Ajoute un produit à la liste des composants
     * @param produit le produit que l'on veux ajouter au composite
     * @throws ProduitException Si le produit est null.
     */
    public void add(Produit produit) throws ProduitException {
        assert null != children;
        if (null == produit) {
            throw new ProduitException("Impossible d'ajouter un produit null");
        }
        children.add(produit);
    }

    /** Enlève un produit de la composition.
     * @param produit le produit à retirer de la composition.
     * @throws ProduitException Si le produit est null ou n'est pas dans la liste.
     */
    public void remove(Produit produit) throws ProduitException {
        assert null != children;
        if (null == produit) {
            throw new ProduitException("Impossible d'enlever un produit null");
        }

        if ( ! children.contains(produit)) {
            throw new ProduitException("Impossible d'enlever le produit, il n'est pas dans la liste");
        }

        children.remove(produit);
    }

    /** Renvoie la liste des composantes du produit sous la forme d'un itérateur.<p>
     * Voir le pattern itérateur.
     * @return La liste des composantes.
     */
    public Iterator getChildren() {
        assert null != children;
        return children.iterator();
    }

    /** Code barre unique d'un produit.
     * @return  le code barre du produit
     */
    public String getCodeBarre() {
        return codeBarre;
    }

    /** Descriptif du produit. Peut être le résultat d'une accumulation de
     * descriptifs si le produit est composé
     * @return  le descriptif composé
     */
    public String getDescriptif() {
        assert null != children;

        StringBuffer result = new StringBuffer();
        result.append(descriptif);
        result.append(" : (");

        for (Iterator i = children.iterator(); i.hasNext(); ) {
            Object objet = i.next();

            assert null != objet : "Un objet null a été trouvé dans la liste des composantes";
            assert objet instanceof Produit : "Un \"non produit\" a été trouvé dans la liste des composantes";

            Produit composant = (Produit)objet;

            result.append(composant.getDescriptif());
            if (i.hasNext()) { // on ajoute une virgule pour séparer les descriptifs
                result.append(", ");
            }
        }

        result.append(" )");
        return result.toString();
    }

    /** Fournit le prix du produit.
     * Peut être calculé.
     * @return  le prix calculé
     */
    public float getPrix() {
        float result = 0;
        for (Iterator i = children.iterator(); i.hasNext(); ) {
            Object objet = i.next();

            assert null != objet : "Un objet null a été trouvé dans la liste des composantes";
            assert objet instanceof Produit : "Un \"non produit\" a été trouvé dans la liste des composantes";

            Produit composant = (Produit)objet;

            result += composant.getPrix();
        }
        result = result * 0.9f; // soit une réducion de 10%
        return result;
    }

    private Collection children;
    private String codeBarre;
    private String descriptif;

}

Et voici un exemple d'utilisation

 
Sélectionnez
Barre maBarre = new Barre();
maBarre.setPrix(25f);
maBarre.setDescriptif("Barre d'haltérophilie");
maBarre.setCodeBarre("BA0001");
maBarre.setLongueur(150f);

Poids leger = new Poids();
leger.setPrix(15f);
leger.setDescriptif("Poids d'haltère");
leger.setCodeBarre("PA0001");
leger.setPoids(0.5f);

Poids moyen = new Poids();
moyen.setPrix(17f);
moyen.setDescriptif("Poids d'haltère");
moyen.setCodeBarre("PA0002");
moyen.setPoids(1f);

Poids lourd = new Poids();
lourd.setPrix(19f);
lourd.setDescriptif("Poids d'haltère");
lourd.setCodeBarre("PA0003");
lourd.setPoids(1.5f);

ProduitComposite haltere = new ProduitComposite();
haltere.add(maBarre);
haltere.add(leger);
haltere.add(moyen);
haltere.add(lourd);