Utiliser le pattern Registry
Date de publication : 15/12/2006
Par
Matthieu Brucher (http://matthieu-brucher.developpez.com/) (Blog)
Le pattern Registry est un
pattern méconnu mais largement utilisé dans de nombreux cas. Il est utilisé pour gérer les paramètres d'un programme - exemple typique, la base de registres de Windows est un registre particulier -, les tests unitaires sont enregistrés dans un registre dans certaines implémentation - comme celles de JUnit -, ou plus simplement des menus.
Introduction
I. L'architecture du pattern
I-A. Descriptif des nécessités des classes
I-B. Proposition de solution
II. Exemple d'implémentation en C++
Conclusion
Introduction
Lorsque l'on désire enregistrer des
objets dans une structure et pouvoir les récupérer de manière transparente, on utilise d'habitude une carte associative. Dans certains cas, on veut plus, à savoir protéger les accès, utiliser une hiérarchie, ... Dans ces cas, on va utiliser un registre pour stocker les données. Traditionnellement un singleton, on peut aussi utiliser plusieurs registres répartis dans plusieurs
instances de
classes englobantes, mais cela est plus rare.
I. L'architecture du pattern
I-A. Descriptif des nécessités des classes
Selon les nécessités, on peut avoir besoin de plusieurs classes différentes. Dans sa version la plus simple, le registre ne fait qu'encapsuler une carte associative.
Dans une version plus complète, une hiérarchie complète que le registre gère est générée. Il faudra à ce moment, outre la classe Registre elle-même, une hiérarchie de classes, une classe de base virtuelle pure qui sera la base de la hiérarchie, une classe fille qui symbolisera les feuilles de l'arbre, et une autre classe fille qui permettra de gérer les noeuds de l'arbre.
I-B. Proposition de solution

Exemple de hiérarchie des classes
La version qu'on va présenter est une version hiérarchique.
Lors de l'ajout d'une nouvelle valeur, de nouveaux noeuds seront créés si besoin est, et un noeud feuille sera créé. Naturellement, s'il faut pouvoir modifier les valeurs entrées dans le registre, il faudra ajouter de nouvelles fonctions pour cela. Ici, nous ferons un simple registre, sans ces fonctions - qui ne sont pas utiles pour un menu ou pour un logiciel de tests -.
Tout d'abord la classe Manager possède une méthode statique pour récupérer l'instance du registre. Ensuite, elle dispose de deux méthodes permettant d'enregistrer une instance d'une classe avec une valeur associée - ici, nous prendrons une chaîne de caractères - et une méthode retournant l'instance stockée lorsqu'on lui présente la chaîne de caractères associée.
La classe de base de la hiérarchie BaseClass n'a pas besoin de contenir grand chose. Elle doit contenir un nom qui est le nom du noeud - utile pour parcourir la hiérarchie à la recherche d'un élément -, une fonction retournant les noeuds fils - s'il y en a -. Dans la classe fille NodeClass qui est responsable de la hiérarchie, on surcharge l'opérateur retournant les noeuds fils pour parcourir l'arbre. Ensuite, la classe fille LeafClass est celle qui contiendra l'instance. Pourquoi utiliser 3 classes alors qu'une seule suffirait ? La raison principale est qu'une seule classe devrait gérer l'arbre et la valeur, ce qui est contraire à un principe bien connu, une classe une responsabilité. Ensuite, le cas ici est très simple, le registre élémentaire. Si celui-ci doit contenir des méthodes supplémentaires, elles seront peut-être différentes selon qu'on soit dans un noeud de l'arbre ou à une feuille.
II. Exemple d'implémentation en C++
On se propose d'utiliser un singleton simple pour la classe Manager, comme pour le précédent article
sur le pattern object Pool. Ce singleton permettra d'enregistrer une instance d'un objet associée à une valeur, ou de récupérer l'instance associée à une valeur. On pourra aussi ajouter d'autres méthodes si besoin est.
Tout comme le précédent tutoriel sur le pattern Object Pool, on laissera au lecteur la compréhension des différentes méthodes utilisées.
\file
#ifndef REGISTRY
#define REGISTRY
#include <string>
#include <map>
class Element;
struct BaseClass
{
@param
BaseClass(const std::string& nom);
@return
\warning
virtual std::map<std::string, BaseClass*>& getNodes();
@return
\warning
virtual Element* getElement();
virtual ~BaseClass(){}
private:
std::string nom;
};
struct NodeClass : public BaseClass
{
@param
NodeClass(const std::string& nom);
@return
virtual std::map<std::string, BaseClass*>& getNodes();
\warning
~NodeClass(){}
private:
std::map<std::string, BaseClass*> subTree;
};
struct LeafClass : public BaseClass
{
@param
@param
LeafClass(const std::string& nom, Element* element);
@return
virtual Element* getElement();
~LeafClass(){}
private:
};
struct Manager
{
@return
Manager& getInstance();
@param
@param
\warning
void addElement(std::string nom, Element* element);
@param
@return
Element* getElement(const std::string& nom) const;
private:
Manager();
Manager(const Manager&);
NodeClass* tree;
};
#endif
|
#include "registry.h"
#include <list>
#include <stdexcept>
@param
@return
std::list<std::string> split(const std::string& nom)
{
std::list<std::string> liste;
size_t start = 0;
for(size_t end = 0; end < nom.size(); ++end)
{
if(nom[end] = ':')
{
liste.push_back(nom.substr(start,start - end - 1));
start = end + 1;
}
}
liste.push_back(nom.substr(start,start - end - 1));
return liste;
}
BaseClass::BaseClass(const std::string& nom)
:nom(nom)
{
}
std::map<std::string, BaseClass*>& BaseClass::getNodes()
{
throw std::runtime_error("Pas de noeuds fils");
}
Element* BaseClass::getElement()
{
throw std::runtime_error("Pas d'élément");
}
NodeClass::NodeClass(const std::string& nom)
:BaseClass(nom), subTree()
{
}
std::map<std::string, BaseClass*>& NodeClass::getNodes()
{
return subTree;
}
LeafClass::LeafClass(const std::string& nom, Element* element)
:BaseClass(nom), element(element)
{
}
Element* LeafClass::getElement()
{
return element;
}
Manager::getInstance()
{
static Manager manager;
return manager;
}
Manager::Manager()
:tree("Root")
{
}
void Manager::addElement(std::string nom, Element* element)
{
std::list<std::string> liste = split(nom);
NodeClass* node = getRoot();
std::list<std::string>::iterator endList = liste.end();
--endList;
for(std::list<std::string>::iterator sousNom = liste.begin(); sousNom != endList; ++sousNom)
{
if(node->getNodes().find(*sousNom) == node->getNodes().end())
{
node->getNodes()[*sousNom] = new NodeClass(*sousNom);
}
std::map<std::string, BaseClass*>::iterator element = node->getNodes().find(*sousNom);
node = static_cast<NodeClass*>(element.second);
}
node->getNodes()[*endList] = new LeafClass(*endList);
}
|
Conclusion
Ce pattern est une base de travail. En général, on veut plus qu'enregistrer ou récupérer une valeur, on peut pouvoir récupérer un sous-arbre, toutes les instances du sous-arbre, modifier l'instance, ... Pour cela, on utilisera d'autres patterns pour aller plus loin.


Copyright © 2006 Matthieu Brucher. 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'à 3 ans de prison et jusqu'à 300 000 E
de dommages et intérêts.
Cette page est déposée à la
SACD.