IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Présentation des principaux design patterns en C++


précédentsommairesuivant

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 :

Diagramme UML du DP composite
Diagramme UML du pattern composite

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 :

 
Sélectionnez
#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

 
Sélectionnez
#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.


précédentsommairesuivant

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2007 Côme David. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.