IV. La fabrique▲
Fabrique : classe dont le rôle est de créer d'autres objets.
IV-A. 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 fatigant. 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 :
IV-B. 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
#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;
};
#endifEt le fichier fabrique.cpp
#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 la clé à été trouvée
if(it != m_map.end())
{
tmp = ((*it).second)->Clone();
}
//on pourrait lancer une exception 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-C. 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 :
//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 :
Fabrique<Figure> fac;La seule vraie 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é, elle est juste passée à la version template qui fait un peu plus peur, mais qui est exactement la même qu'avant, à la généricité près.



