Composite (patron de conception)
En génie logiciel, un patron composite est un patron de conception (design pattern) structurel[1]. Ce patron permet de concevoir une structure arborescente, par exemple une représentation d'un dossier, ses sous-dossiers et leurs fichiers.
Motivation
En programmation orientée objet, le patron composite propose une structure récursive permettant d'implémenter avec la même interface logicielle sur les feuilles et les composites afin qu'ils soient manipulés de la même manière[1].
Structure
- Composant
- est l'abstraction pour tous les composants, y compris ceux qui sont composés
- déclare l'interface pour le comportement par défaut
- Feuille
- représente un composant n'ayant pas de sous-éléments
- implémente le comportement par défaut
- Composite
- représente un composant pouvant avoir des sous-éléments
- stocke des composants enfants et permet d'y accéder
- implémente un comportement en utilisant les enfants
- Client
- manipule les objets de la composition à travers l'interface de la classe Composant
Exemple C++11
L'exemple qui suit, écrit en C++11, met en œuvre une classe 'Component' qui peut être un fichier (File), un ensemble de fichiers ou un répertoire (Directory). Chaque élément peut être parcouru (méthode virtuelle 'ls_r').
#include <iostream> // pour std::cout
#include <list> // pour std::list
#include <memory> // pour std::unique_ptr
// Cet exemple représente la hiérarchie d'un système de fichiers : fichiers/répertoires
// L'objet abstrait qui sera composé dans le composite
// Dans notre exemple, il s'agira d'un fichier ou d'un répertoire
class Component {
public:
virtual ~Component() {} // Le destructeur d'une interface doit être virtuel
public:
// Parcours récursif de l'arbre
virtual void ls_r() = 0;
protected:
std::string name_;
};
// Classe 'Leaf'
class File : public Component {
public:
virtual void ls_r() override {
std::cout << name_ << std::endl;
}
};
// Classe 'Composite'
// Le repertoire est aussi un 'Component' car il peut être sous-répertoire
class Directory : public Component {
public:
virtual void ls_r() override {
std::cout << "[" << name_ << "]" << std::endl;
for( auto &component_ptr : component_ptrs_ ) {
component_ptr->ls_r();
}
}
protected:
// Liste des composants
std::list< std::unique_ptr< Component > > component_ptrs_;
};
Exemple Java
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 trois 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();
}
}
Exemple en PHP 5
<?php
class Component
{
// Attributes
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; }
// Method
public function getPath() { return $this->getBasePath().'/'.$this->getName(); }
}
class CFile extends Component
{
// Attributes
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; }
// Methods of Component class
public function getName() { return parent::getName().'.'.$this->getType(); }
public function getPath() { return parent::getPath().'.'.$this->getType(); }
}
class CDirectory extends Component
{
// Attributes
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; }
// Methods
public function addChild(Component $child)
{
$child->setParent($this);
$this->childs[] = $child;
}
public function showChildsTree($level = 0)
{
echo "<br/>".str_repeat(' ', $level).$this->getName();
foreach($this->getChilds() as $child)
{
if($child instanceof self)
{
$child->showChildsTree($level+1);
}
else
{
echo "<br/>".str_repeat(' ', $level+1).$child->getName();
}
}
}
}
?>
Exemple d'utilisation (example of use):
<?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 (result on the screen) :
root dir1 file1.txt dir2 dir4 doc.pdf dir3
Exemple C#
Produisant un résultat similaire à l'exemple en php, une variante en C#.
Dans ce code la méthode Display() correspond à la méthode opération() du diagramme de classes.
abstract class Component
{
public string Name { get; set; }
public abstract void Display(int indentLevel = 0);
protected void Indent(int level)
{
for(var i = 0; i <= level; i++)
Console.Write(" ");
}
}
class Leaf : Component
{
public Leaf() { }
public Leaf(string name)
{
this.Name = name;
}
public override void Display(int indentLevel = 0)
{
this.Indent(indentLevel);
Console.WriteLine("Leaf : {0}", this.Name);
}
}
class Composite : Component
{
public IList<Component> SubComponents { get; set; }
public Composite() { }
public Composite(string name, params Component[] subComponents)
{
this.Name = name;
this.SubComponents = new List<Component>(subComponents);
}
public override void Display(int indentLevel = 0)
{
this.Indent(indentLevel);
Console.WriteLine("Composite : {0}", this.Name);
foreach(Component subComponent in this.SubComponents)
subComponent.Display(indentLevel + 1);
}
}
class Program
{
static void Main(string[] args)
{
// Exemple d'instanciation d'une structure composite:
Component cadre = new Composite()
{
Name = "fond d écran",
SubComponents = new Component[] {
new Composite() {
Name = "ciel",
SubComponents = new Component[] {
new Leaf() { Name="soleil" }}},
new Composite() {
Name = "mer",
SubComponents = new Component[] {
new Leaf() { Name = "bouée" },
new Composite() {
Name="bateau",
SubComponents = new Component[] {
new Leaf() { Name="voile" },
new Leaf() { Name="coque" }}}}}}};
// Et voilà le pourquoi de l'utilisation du pattern:
// un seul appel à Display dessine tout le cadre.
cadre.Display();
// Le même cadre défini de façon alternative via les constructeurs:
Component cadre2 = new Composite("fond d écran",
new Composite("ciel",
new Leaf("soleil")),
new Composite("mer",
new Leaf("bouée"),
new Composite("bateau",
new Leaf("voile"),
new Leaf("coque"))));
// Et on obtient le même résultat:
cadre2.Display();
Console.ReadLine();
}
}
Et le résultat (en double):
Composite : fond d écran
Composite : ciel
Leaf : soleil
Composite : mer
Leaf : bouée
Composite : bateau
Leaf : voile
Leaf : coque
Notes et références
- Gamma, Erich., Helm, Richard, consultant., Johnson, Ralph. et Lasvergères, Jean-Marie., Design patterns : catalogue de modèles de conception réutilisables, Paris, Vuibert, , 459 p. (ISBN 2-7117-8644-7 et 9782711786442, OCLC 42617283, lire en ligne)