Ansi/iso c++ : 081 : classe fabrique (usine)

Contenu du snippet

Voici une construction de "classe fabrique" (ou usine), c'est à dire une classe capable de fournir de nouveaux objet dont la la classe est déterminé dynamiquement.
Ici, la classe est coisie en fonction du premier paramettre passé aux programme.
La class 'A' est abstraite et servira à définir l'interface commun aux différente classes de la fabrique. Ces classe sont déclaré à la fin du code, pour vous montrer qu'elle sont facilement extenssible (nouvelles classe ...)
Je vai encore apporter plus d'explications par la suite (mises à jours prochaines)...

Source / Exemple :


#include <iostream>
   #include <string>
   #include <map>

   template<class CA>
   class AT;

   class A
   {
      public:

         static A * get_new( std::string const & class_name );

         virtual void cout_name() = 0;

      private:

         typedef A *(*GetNewAFct)();
         typedef std::map<std::string,GetNewAFct> StrMapNewA;
         static StrMapNewA a_creators;

         template<class CA>
         class Register
         {
            public:
               Register();
         };

      template<class CA>
      friend class Register;
   };

   template<class CA>
   class AT : public A
   {
      public:

         static A * get_new();

      private:

         static char const * name();
         static A::Register<CA> m_register;

      friend class A::Register<CA>;
   };

   template<class CA>
   A::Register<CA>::Register()
   {
      a_creators.insert(
         StrMapNewA::value_type(
            std::string( AT<CA>::name() ), & AT<CA>::get_new
         )
      );
   }

   template<class CA>
   A::Register<CA> AT<CA>::m_register;

   template<class CA>
   A * AT<CA>::get_new()
   { return new CA; }

   A::StrMapNewA A::a_creators;

   A * A::get_new( std::string const & class_name )
   {
      StrMapNewA::iterator i = a_creators.find( class_name );
      if( i != a_creators.end() )
         return (*i).second();
      else
         return static_cast<A*>(0);
   }

   int main( int argc, char* argv[] )
   {
      using namespace std;

      if( argc > 1 )
      {
         A * pA = A::get_new( string( argv[1] ) );
         if( pA )
         {
            pA->cout_name();
            /* ... */
         }
         else
            cout << "No class for this name :-(" << endl;
      }
      else
         cout << "You must give a class name !" << endl;

      cin.ignore(1,'\n');
   }

   class A1
   : public AT<A1>
   {
      // A virtual function
      //
         public:
            void cout_name()
            { std::cout << "Yoho A1 !!" << std::endl; }
   };
   template<> char const * AT<A1>::name() { return "A1"; }
   template class AT<A1>;

   class A2
   : public AT<A2>
   {
      // A virtual function
      //
         public:
            void cout_name()
            { std::cout << "Hehe A2 ^_^" << std::endl; }
   };
   template<> char const * AT<A2>::name() { return "A2"; }
   template class AT<A2>;

Conclusion :


Je déclare une classe template, 'AT', dont hériteront nos classe CA (A1, A2, ...).
La classe 'A', dont hérite 'AT' (oui oui !) contient donc les fonction d'interface commune aux 'CA', ici une fonction virtuel qu'implémentera chaque class 'CA'.
La fonction statique permet d'obtenir l'instanciation d'une classe 'CA' à partir de son "nom" ("C1", "C2", ...). C'est le but de la manoeuvre :-)
En privé, 'A' possède une varribale statique lui permettant de stoquer une table de correspondance entre une chaine de caractères et un pointeur de fonction retournant l'adresse d'un nouvelle objet de classe 'A'. En pratique, pour chaque classe 'CA', il y aura une fonction qui instantiera un objet de classe 'CA' correspondant et en retournera un pointeur en tant 'A'.
Bon, et pour faciliter l'enregistrement de chaque classe 'CA', une classe template imbriqué, 'A::Register<CA>', amie bien entendu ;-)
Bon, revenons maintenant à 'AT<CA>', dont hérite les 'CA'.
En public, elle possède une fonction statique (une fonction par 'CA' !), celle-la même dont l'adresse est place dans la table de correspondance ('A::a_creators') !
En privé, une fonction statique, 'name', qui retournera le nom de la classe. Pour chaque class 'CA', nous spécialiserons cette fonction (patience ;).
Et !, une variable statique (une variable par 'CA' !), un objet de classe 'A::Register<CA>'.
Bien entendu, on est tous amis ;-)
Maintenant, que fait cette fameuse classe 'Register<CA>' ? Hé bien, à ça création (avant l'entré dans la fonction main donc, puisse que statique de 'AT<CA>'), elle enregistre la classe 'CA', en insérant un couple créé à partir du nom retourné par 'AT<CA>::name()' (spécialisée !) et l'adresse de la fonction 'AT::<CA>::get_new' (statique !). Ainsi, l'insertion est automatique pour toute classe 'CA', c'est à dire héritant de 'AT<CA>' et pour laquelle 'AT<CA>::name' est spécialisé (plus encore une petite chose...;).
Ne pas oublier de définir les membre statique, en locurrence 'AT<CA>::m_register'.
La fonction 'AT<CA>::get_new()' est trivialement simple.
Définition, maintenant de notre table de correspondance, 'A::a_creators'.
La fonction la plus important, 'A::get_new( class_name )' est celle qui vat chercher dans la table de correspondance, la fonction adéquat pour la création de l'objet dont la classe sera déterminé par 'class_name'. Pour cela, nous utilisont la fonction 'map<>::find( key )'. Dès lors que l'itérateur retourné est "valide", nous utilison la seconde partie de l'élément, à savoir le pointeur de fonction, pour appeler cette fonction et retourner, directement, l'adresse ainsi obtenu, du nouvelle objet. Ici, le chois est fait, en cas d'échec, de retourner un pointeur nul. Mais vous pourriez également émettre une exeption.
Dans la fonction 'main( argc, arv )', nous appelons, tout simplement 'A::get_new' en passant un 'std::string' construit à partir du 1er argument ('argv[1]'). Pour "démonstration", nous envoyons via le flux de sortie, le "nom" de la classe, ici obtenu par l'appel de la fonction virtuelle 'A::cout_name'.
Il ne nous reste plus qu'à concrétiser quelques classe 'CA'.
C'est très simple, chaque classe 'CA' doit hériter de 'AT<CA' (par exemple 'C1' hérite de 'AT<C1>'). Elle doit implémenter (de préférence ;) les fonctions virtuelles (ici, 'cout_name'). Il faut spécialiser la fonction 'AT<CA>::name', de sorte à lui donner un nom (ici, nous respectons le nom c++ de la classe ("A1", "A2", ...), mais vous pourriez mettre "bannane" et "abricot"). C'est ce nom qui est utilisé dans la table de correspondance. Et finalement, petit détail de grande importance, "instancier" la classe 'AT<CA', sans quoi vous n'aurrez de définition des membres en question dans aucun module (.obj ou .o) à l'édition des liens !
Bon, hé bien voila, n'hésitez pas à me poser des questions, de préférence sur le ng fr.comp.lang.c++ :-)

A voir également

Vous n'êtes pas encore membre ?

inscrivez-vous, c'est gratuit et ça prend moins d'une minute !

Les membres obtiennent plus de réponses que les utilisateurs anonymes.

Le fait d'être membre vous permet d'avoir un suivi détaillé de vos demandes et codes sources.

Le fait d'être membre vous permet d'avoir des options supplémentaires.