VI. Le composite▲
Définition : Le composite est un pattern qui permet de manipuler un ensemble d'objets comme un seul.
VI-1. Quand a-t-on besoin de celui-ci ?▲
Hors du code, le pattern composite est l'un des patterns que l'on croise le plus souvent sans s'en rendre compte puisque dès que l'on navigue dans l'arborescence d'un disque dur, nous le côtoyons.
En effet, quand nous naviguons, nous pouvons manipuler un dossier comme bon nous semble, quoi que contienne ce dossier (autres dossiers, fichiers...).
Et c'est précisément ce que propose le composite : pouvoir s'abstraire du nombre d'objets réels et de les manipuler comme s'il n'y en avait qu'un.
De ce fait le composite va tout d'abord fournir une interface pour permettre de manipuler les objets, puis deux classes qui héritent de cette interface. Une qui représente un objet 'terminal' et une autre pour représenter l'objet composite en lui même.
De ce fait, le diagramme UML du pattern est :
VI-2. Première implémentation▲
En reprenant l'exemple de l'arborescence d'un disque dur sous un système unix, un premier code peut être :
#include
<iostream>
#include
<string>
#include
<vector>
#include
<boost/shared_ptr.hpp>
//un fichier de base
class
FichierAbstrait
{
static
const
int
m_taille;
protected
:
std::
string m_nom;
public
:
virtual
int
GetTaille() const
{
return
m_taille; }
virtual
void
Afficher() const
{
std::
cout<<
m_nom<<
" "
<<
GetTaille();}
virtual
~
FichierAbstrait() =
0
;
FichierAbstrait(const
std::
string&
nom):m_nom(nom){}
}
;
//un fichier terminal
class
Fichier :public
FichierAbstrait
{
private
:
std::
string m_type;
public
:
void
Afficher() const
{
FichierAbstrait::
Afficher();
std::
cout<<
" "
<<
m_type<<
std::
endl;
}
Fichier(const
std::
string&
nom,const
std::
string&
type):
FichierAbstrait(nom),m_type(type)
{}
}
;
//un conteneur
class
Dossier : public
FichierAbstrait
{
private
:
std::
vector<
boost::
shared_ptr<
FichierAbstrait>
>
m_listfichier;
public
:
void
Afficher() const
;
int
GetTaille() const
;
Dossier&
Add(Dossier*
file)
{
m_listfichier.push_back(boost::
shared_ptr<
Dossier>
(file));
return
(*
file);
}
void
Add(Fichier*
file)
{
m_listfichier.push_back(boost::
shared_ptr<
Fichier>
(file));
}
Dossier(const
std::
string&
path):FichierAbstrait(path)
{}
}
;
Et dans le fichier composite.cpp
#include
"composite.h"
const
int
FichierAbstrait::
m_taille=
1
;
FichierAbstrait::
~
FichierAbstrait() {}
void
Dossier::
Afficher() const
{
//pour chaque élément du vecteur, on l'affiche
std::
vector<
boost::
shared_ptr<
FichierAbstrait>
>
::
const_iterator itb=
m_listfichier.begin();
const
std::
vector<
boost::
shared_ptr<
FichierAbstrait>
>
::
const_iterator ite=
m_listfichier.end();
std::
cout<<
"["
<<
m_nom<<
"]"
;
for
(;itb!=
ite;itb++
)
{
(*
itb)->
Afficher();
}
;
}
int
Dossier::
GetTaille() const
{
//on fait la somme de la taille de chaque élément
int
somtaille=
0
;
std::
vector<
boost::
shared_ptr<
FichierAbstrait>
>
::
const_iterator itb=
m_listfichier.begin();
const
std::
vector<
boost::
shared_ptr<
FichierAbstrait>
>
::
const_iterator ite=
m_listfichier.end();
for
(;itb!=
ite;itb++
)
{
somtaille+=
(*
itb)->
GetTaille();
}
return
somtaille;
}
int
main(void
)
{
Dossier root("/"
);
root.Add(new
Dossier("home/"
)).Add(new
Dossier("david/"
)).Add(new
Fichier("composite.h"
,"text"
));
root.Afficher();
}
Quelques petits points à souligner.
Le premier d'entre eux est sur le destructeur de FichierAbstrait. Comme son nom l'indique, cette classe est abstraite, elle doit donc posséder au moins une fonction virtuelle pure. Or dans notre cas, aucune ne semble convenir. Dans ce cas, il faut se rabattre sur le destructeur et le rendre virtuel. Néanmoins, il se pose un problème. Lors de la destruction des classes dérivés, les objets vont forcément appeler le constructeur de la classe parente, on ne peut donc pas laisser ce destructeur sans définition et c'est pourquoi on le définit dans le C++. La plupart d'entre vous doivent se dire que cela va à l'encontre de ce qu'ils savent des fonctions virtuelles. Il faut bien noter qu'une fonction virtuelle peut être redéfinie par une classe dérivée alors qu'une fonction virtuelle pure doit l'être si on veut que la classe soit instanciable. Néanmoins, le C++ ne nous empêche de donner une définition à une fonction virtuelle pure, elle est juste facultative.
L'utilisation des boost::shared_ptr (comme dans le cas du décorateur) permet d'allouer dynamiquement les dossiers et fichiers tout en étant sûr qu'il seront copiés et détruits convenablement. Si on souhaite utiliser des objets alloués sur la pile, il ne faut pas utiliser des shared_ptr.