Présentation des principaux design patterns en C++


précédentsommairesuivant

IV. La fabrique

Fabrique : classe dont le rôle est de créer d'autres objets.

IV-1. Un besoin de fabrique

Il est courant en C++ d'avoir comme design une hiérarchie de classes dérivant toutes d'une même interface pour profiter du polymorphisme. Mais gérer manuellement la création d'objets peut vite être fatiguant. La solution est alors de se reposer sur la fabrique qui, comme son nom l'indique, va fabriquer des objets.

Pour fabriquer, ou créer, des objets, elle va disposer d'une méthode Create prenant en paramètre le type d'objet à créer et d'une méthode Register pour associer à chaque type à créer une "action". On en tire donc le diagramme suivant :

Diagramme UML de la fabrique
Diagramme UML de la fabrique

IV-2. Première implémentation

Avant toute chose, il faut préciser comment seront créés les objets. En effet, sur ce point, plusieurs stratégies sont envisageables telle la création via new ou celle par clonage. Pour ma part, j'ai choisi celle basée sur le clonage. Mais vous trouverez facilement d'autres implémentations sur le web.

D'après l'analyse et la remarque précédente, on peut écrire une première version de la fabrique comme suit :
fabrique.h

 
Sélectionnez

#ifndef FABRIQUE_H
#define FABRIQUE_H

#include <map>
#include <string>

//la classe prototype abordé en II
template <class T> class Prototype
{
        public:
        virtual ~Prototype(){}        
        virtual T* Clone() const =0 ;
};

//l'interface "Figure" 
class Figure : public  Prototype<Figure>
{
        public: 
        virtual void SeDessiner()=0;
};

class Carre : public Figure 
{
public:
        Figure* Clone() const;
        void SeDessiner();
        
};

class Cercle : public Figure 
{
public:
        Figure* Clone() const ;
        void SeDessiner();
};

//La fabrique à proprement parler
class FactoryFigure 
{
public:
       static std::map<std::string,Figure*> m_map;

public:
        //Fonction qui associe clé <=> prototype
        static void Register(const std::string& key,Figure* obj);

        //Celle qui va créer les objets
        Figure* Create(const std::string& key) const;
};
#endif

Et le fichier fabrique.cpp

 
Sélectionnez

#include <iostream>
#include <iterator>

#include "fabrique.h"

using namespace std;

Figure* Carre::Clone() const 
{
 return new Carre(*this);
}

void Carre::SeDessiner()
{
        cout<<"Je suis un Carre"<<endl;
}
        
Figure* Cercle::Clone() const 
{ 
        return new Cercle(*this);
}

void Cercle::SeDessiner()
{
        cout<<"Je suis un Cercle"<<endl;
}

std::map<string,Figure*> FactoryFigure::m_map= std::map<string,Figure*>();

void FactoryFigure::Register(const string& key,Figure* obj)
{
        //si la clé n'est pas déjà présente
        if(m_map.find(key)==m_map.end())
                   {
                //on ajoute l'objet dans la map
                m_map[key]=obj;
                }

       //on pourrait détruire obj mais cette tâche ne revient pas à Register
}

Figure* FactoryFigure::Create(const std::string& key) const
{
                Figure* tmp=0;
            std::map<string, Figure*>::const_iterator it=m_map.find(key);
    
                //si l'itérateur ne vaut pas map.end(), cela signifie que que la clé à été trouvée
                if(it!=m_map.end())
                {
                          tmp=((*it).second)->Clone();
                      }

                //on pourrait lancer une exeption si la clé n'a pas été trouvée

                return tmp;
}

int main(void)
{
  //notre fabrique
  FactoryFigure fac;

 //on enregistre des types
  FactoryFigure::Register("Carre",new Carre);
  FactoryFigure::Register("Cercle",new Cercle);

  //on crée des objets via la fabrique
  Figure *c=fac.Create("Cercle");
  Figure *ca=fac.Create("Carre");

  c->SeDessiner();
  ca->SeDessiner();

  delete c;
  delete ca;

  return 0;
}

Ce code ne présente aucune grande difficulté. Pour chaque clé, on associe un prototype que la méthode Create se charge de cloner, rien de plus.

Mais ce code est très mauvais du point de vue de la conception. En effet, la fabrique est intimement liée au type d'objet qu'elle va créer. De ce fait, pour créer une autre fabrique, il faut copier-coller du code ce qui est très mauvais signe.

IV-3. Un code plus réutilisable avec les templates

Comme on l'a vu, une fabrique est liée à deux choses : le type d'objet qu'elle va créer et la clé qu'elle va utiliser pour les indexer. De ce fait, il 'suffit' de transformer Figure et std::string en deux paramètres pour une classe template afin d'obtenir une très grande modularité.

En effet, avec les templates, on peut indexer n'importe quel type d'objet avec n'importe quoi (des ints, des strings...). De ce fait, le code de la fabrique devient :

 
Sélectionnez

//fichier fabrique.h
template <class Object,class Key=string> class Factory
{
      static std::map<Key,Object*> m_map;
public:

        static void Register(Key key,Object* obj);
        Object* Create(const Key& key);
};

//fichier fabrique.cpp
template <class Object,class Key> std::map<Key,Object*> Factory<Object,Key>::m_map=std::map<Key,Object*>();

template <class Object,class Key> void Factory<Object,Key>::Register(Key key,Object* obj)
{
        if(m_map.find(key)==m_map.end())
        {
               m_map[key]=obj;
        }
}

template <class Object,class Key> Object* Factory<Object,Key>::Create (const Key& key)
{
                Object* tmp=0;
                typename std::map<Key, Object*>::iterator it=m_map.find(key);
    
                if(it!=m_map.end())
                {
                      tmp=((*it).second)->Clone();
                      }

                return tmp;
}

Et son utilisation est comme celle d'un vecteur :

 
Sélectionnez

Fabrique<Figure> fac; 

La seule vrai innovation est la présence du typename devant l'itérateur. Pour sa justification reportez-vous à la FAQ. En ce qui concerne l'initialisation de m_map, elle n'a pas changée, elle est juste passé à la version template qui fait un peu plus peur mais qui est exactement la même qu'avant, à la généricité près.


précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

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 et 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.