Patrons de conception/Objet composite


Dans ce patron de conception, un objet composite est constitué d'un ou de plusieurs objets similaires (ayant des fonctionnalités similaires). L'idée est de manipuler un groupe d'objets de la même façon que s'il s'agissait d'un seul objet. Les objets ainsi regroupés doivent posséder des opérations communes, c'est-à-dire un "dénominateur commun".

Patron de conception
Catégorie : « Gang of Four »Structure
Nom français : Objet composite
Nom anglais : Composite
Créer une classe de manipulation de composants en utilisant la même interface commune que les composants

Quand l'utiliser

modifier

Vous avez l'impression d'utiliser de multiples objets de la même façon, souvent avec des lignes de code identiques ou presque. Par exemple, lorsque la seule et unique différence entre deux méthodes est que l'une manipule un objet de type Carré, et l'autre un objet Cercle. Lorsque, pour le traitement considéré, la différenciation n'a pas besoin d'exister, il serait plus simple de considérer l'ensemble de ces objets comme homogène.

Un exemple

modifier

Un exemple simple consiste à considérer l'affichage des noms de fichiers contenus dans des dossiers :

  • Pour un fichier, on affiche ses informations.
  • Pour un dossier, on affiche les informations des fichiers qu'il contient.

Dans ce cas, le patron composite est tout à fait adapté :

  • Objet est de façon générale ce qui peut être contenu dans un dossier : un fichier ou un dossier,
  • ObjetSimple est un fichier, sa méthode affiche() affiche simplement le nom du fichier,
  • ObjetComposite est un dossier, il contient des objets (c'est à dire des fichiers et des dossiers). Sa méthode affiche() parcourt l'ensemble des objets qu'il contient (fichier ou dossier) en appelant leur méthode affiche().

Diagramme de classes UML

modifier

Le patron de conception Objet composite peut être représenté par le diagramme de classes UML suivant :

 
Diagramme des classes UML du patron de conception Objet composite
  • Objet
    • déclare l'interface pour la composition d'objets,
    • met en œuvre le comportement par défaut.
  • ObjetSimple
    • représente les objets manipulés, ayant une interface commune.
  • ObjetComposite
    • définit un comportement pour les composants ayant des enfants,
    • stocke les composants enfants,
    • met en œuvre la gestion des composants enfants.

La classe utilisatrice manipule les objets de la composition à travers l'interface Objet.

Implémentations

modifier
// Cet exemple représente la hiérarchie d'un système de fichiers : fichiers/répertoires

class Composant
{
// L'objet abstrait qui sera 'composé' dans le composite
// Dans notre exemple, il s'agira d'un fichier ou d'un répertoire

    public:
        // Parcours récursif de l'arbre
        // (Utilisez plutôt le patron de conception "Visiteur" à la place de cette fonction)
        virtual void listerNoms() = 0 ;

    protected:
        std::string name;
};

class File : public Composant
{
    public:
        void listerNoms()
        {
            std::cout << name << std::endl;
        }
};

class Repertoire : public Composant
{
//Le répertoire est aussi un 'composant' car il peut être sous-répertoire
    protected:
        std::list<Composant*> files; // Liste des composants enfants

    public:
        void listerNoms()
        {
            std::cout << "[" << name << "]" << std::endl;
            for( std::list<Composant*>::iterator it = files.begin();
                it != files.end(); it ++ )
            {
                it->listerNoms();
            }
        }
};

L'exemple qui suit, écrit en Java, met en œuvre une classe graphique qui peut être ou bien une ellipse ou une composition de différents graphiques. Chaque graphique peut être imprimé.

Il pourrait être étendu en y ajoutant d'autres formes (rectangle etc.) et méthodes (translation etc.).

import java.util.ArrayList;

interface Graphic
{
    //Imprime le graphique.
    public void print();
}

class CompositeGraphic implements Graphic
{
    //Collection de graphiques enfants.
    private ArrayList<Graphic> mChildGraphics = new ArrayList<Graphic>();

    //Imprime le graphique.
    public void print()
    {
        for (Graphic graphic : mChildGraphics)
        {
            graphic.print();
        }
    }

    //Ajoute le graphique à la composition.
    public void add(Graphic graphic)
    {
        mChildGraphics.add(graphic);
    }

    //Retire le graphique de la composition.
    public void remove(Graphic graphic)
    {
        mChildGraphics.remove(graphic);
    }

}

class Ellipse implements Graphic
{
    //Imprime le graphique.
    public void print()
    {
        System.out.println("Ellipse");
    }

}

public class Program
{
    public static void main(String[] args)
    {
        //Initialise quatre ellipses
        Ellipse ellipse1 = new Ellipse();
        Ellipse ellipse2 = new Ellipse();
        Ellipse ellipse3 = new Ellipse();
        Ellipse ellipse4 = new Ellipse();

        //Initialise three graphiques composites
        CompositeGraphic graphic = new CompositeGraphic();
        CompositeGraphic graphic1 = new CompositeGraphic();
        CompositeGraphic graphic2 = new CompositeGraphic();

        //Composes les graphiques
        graphic1.add(ellipse1);
        graphic1.add(ellipse2);
        graphic1.add(ellipse3);

        graphic2.add(ellipse4);

        graphic.add(graphic1);
        graphic.add(graphic2);

        //Imprime le graphique complet (quatre fois la chaîne "Ellipse").
        graphic.print();
    }
}
<?php
class Component
{
    // Attributs
    private $basePath;	
    private $name;
    private $parent;
 	
    public function __construct($name, CDirectory $parent = null)
    {
        // Debug : echo "constructor Component";
        $this->name = $name;
        $this->parent = $parent;
        if($this->parent != null)
        {
            $this->parent->addChild($this);
            $this->basePath = $this->parent->getPath();
        }
        else
        {
            $this->basePath = '';			
        }
    }

    // Getters	
    public function getBasePath() { return $this->basePath; }
    public function getName() { return $this->name; }
    public function getParent() { return $this->parent; }

    // Setters
    public function setBasePath($basePath) { $this->basePath = $basePath; }
    public function setName($name) { $this->name = $name; }
    public function setParent(CDirectory $parent) { $this->parent = $parent; }

    // Méthode
    public function getPath() { return $this->getBasePath().'/'.$this->getName(); }
}

class CFile extends Component
{
    // Attributs
    private $type;

    public function __construct($name, $type, CDirectory $parent = null)
    {
        // Debug : echo "constructor CFile";
        $this->type = $type;

        // Retrieve constructor of Component
        parent::__construct($name, $parent);	
    }

    // Getters	
    public function getType() { return $this->type; }

    // Setters
    public function setType($type) { $this->type = $type; }

    // Méthodes de la classe Component
    public function getName() { return parent::getName().'.'.$this->getType(); }
    public function getPath() { return parent::getPath().'.'.$this->getType(); }
}

class CDirectory extends Component
{

    // Attributs
    private $childs;
	
    public function __construct($name, CDirectory $parent = null)
    {
        // Debug : echo "constructor CDirectory";
        $this->childs = array();

        // Retrieve constructor of Component
        parent::__construct($name, $parent);
    }

    // Getters	
    public function getChilds() { return $this->childs; }

    // Setters
    public function setChilds($childs) { $this->childs = $childs; }

    // Méthodes
    public function addChild(Component $child)
    {
        $child->setParent($this);
        $this->childs[] = $child;
    }

    public function showChildsTree($level = 0)
    {
        echo "<br/>".str_repeat('&nbsp;', $level).$this->getName();
        foreach($this->getChilds() as $child)
        {
            if($child instanceof self)
            {
                $child->showChildsTree($level+1);
            }
            else
            {
                echo "<br/>".str_repeat('&nbsp;', $level+1).$child->getName();
            }	
        }
    }
}
?>

Exemple d'utilisation :

<?php
$root = new CDirectory('root');
$dir1 = new CDirectory('dir1', $root);
$dir2 = new CDirectory('dir2', $root);
$dir3 = new CDirectory('dir3', $root);
$dir4 = new CDirectory('dir4', $dir2);
$file1 = new CFile('file1','txt', $dir1);
$file2 = new CFile('doc', 'pdf', $dir4);

$root->showChildsTree();
?>

Résultat à l'écran :

root
 dir1
  file1.txt
 dir2
  dir4
   doc.pdf
 dir3

Produisant un résultat similaire à l'exemple en PHP, une variante en C#.

Dans ce code la méthode Draw() correspond à la méthode opération() du diagramme de classes.

abstract class Composant
{
    public int Level = 0;
    public string Nom;
    public virtual void Draw()
    {
        for (var i = 0; i < Level; i++)
            Console.Write("    ");
    }
}

class Feuille : Composant
{
    public override void Draw()
    {
        base.Draw();
        Console.WriteLine("Feuille : {0}", Nom);
    }
}

// Par simplicité, ni méthode Add ni Remove ni GetChild.
class Composite : Composant
{
    public Composant[] Composants; // serait private s'il y avait une méthode Add.
    public override void Draw()
    {
        base.Draw();
        Console.WriteLine("Composite : {0}", Nom);
        foreach (Composant composant in Composants)
        {
            composant.Level = this.Level + 1;
            composant.Draw();
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        //__________________________________________________________________________
        // On crée en général la structure par de multiples appels à la méthode Add.

        var cadre= new Composite() {
            Nom = "fond d'écran",
            Composants = new Composant[] {
                new Composite() {
                    Nom = "ciel",
                    Composants = new Composant[] {
                        new Feuille() { Nom="soleil" }}},
                new Composite() {
                    Nom = "mer",
                    Composants = new Composant[] {
                        new Composite() {
                            Nom="bateau",
                            Composants = new Composant[] {
                                new Feuille() { Nom="kevin" },
                                new Feuille() { Nom="katsumi" }}}}}}
        };

        //__________________________________________________________________________
        // Et voilà le pourquoi de l'utilisation du patron :
        //     un seul appel à Draw dessine tout l'écran.

        cadre.Draw();
    }
}

Et le résultat :

Composite : fond d'écran
    Composite : ciel
        Feuille : soleil
    Composite : mer
        Composite : bateau
            Feuille : kevin
            Feuille : katsumi