Technique – Microfabriques

Cet article pousse un peu plus loin la technique d'optimisation par savoir discret, sur laquelle je vous invite à lire au préalable. Les techniques qui y sont présentées présument que la lectrice ou le lecteur comprend les idées d'héritage, de polymorphisme et de classes imbriquées.

Vous trouverez sans doute utile de consulter les quelques articles pointés au passage pour mieux comprendre les techniques appliquées ici.

Cet exemple est volontairement simpliste; nous ne l'utiliserons que pour exprimer une technique de programmation. On peut faire bien mieux en ayant recours à des facettes et au support à l'internationalisation du code.

Imaginons un système simple et capable d'afficher des messages d'erreur à la console en fonction de la langue courante. Pour nos fins, le système sera simplifié au maximum :

Une version simpliste mais raisonnable de ce schème serait :

#include "lexical_cast.h" // mais avec std::wstringstream plutôt que std::stringstream
#include <string>
#include <iostream>
//
// Classe abstraite servant d'interface
//
struct Plaignard
{
   enum MessageId
   {
      MSG_DEFAULT = 0,
      MSG_OUTOFMEMORY, // manque de mémoire
      MSG_IOERROR      // erreur d'entrée/ sortie
   };
   using str_type = std::wstring;
   str_type virtual message(MessageId) const = 0;
   virtual ~Plaignard() = default;
};
//
// Catégories de plaignards
//
struct PlaignardFrancais : Plaignard
{
   str_type message(MessageId mid) const
   {
      str_type msg;
      switch (mid)
      {
      case MSG_OUTOFMEMORY:
         msg = L"Manque de mémoire";
         break;
      case MSG_IOERROR:
         msg = L"Erreur d'entrée/ sortie";
         break;
      default:
         msg = L"Erreur inconnue, code " + lexical_cast<str_type>(mid);
         break;
      }
      return msg;
   }
};
struct PlaignardAnglais : Plaignard
{
   str_type message(MessageId mid) const
   {
      str_type msg;
      switch (mid)
      {
      case MSG_OUTOFMEMORY:
         msg = L"Out of memory";
         break;
      case MSG_IOERROR:
         msg = L"I/O error";
         break;
      default:
         msg = L"Unknown error, code " + lexical_cast<str_type>(mid);
         break;
      }
      return Msg;
   }
};
struct PlaignardAllemand : Plaignard
{
   str_type message(MessageId mid) const
   {
      str_type msg;
      switch (mid)
      {
      case MSG_OUTOFMEMORY:
         msg = L"Aus Gedächtnis heraus";
         break;
      case MSG_IOERROR:
         msg = L"Input/Output Störung";
         break;
      default:
         msg = L"unbekannte Störung, Code " + lexical_cast<std::wstring>(mid);
         break;
      }
      return msg;
   }
};
//
// Fabricant de plaignard
//
#include <memory>
class FabriquePlaignard
{
public:
   enum Categorie
   {
      Francais, Anglais, Allemand,
      Defaut = Francais // doit être le dernier
   };
   static std::unique_ptr<Plaignard> creer(Categorie cat = Defaut)
   {
      using namespace std;
      switch (cat)
      {
      case Francais:
         return make_unique<PlaignardFrancais>();
         break;
      case Anglais:
         return make_unique<PlaignardAnglais>();
         break;
      case Allemand:
         return make_unique<PlaignardAllemand>();
         break;
      default:
        ;
      }
      return nullptr;
   }
};
//
// Petit programme de démonstration
//
int main()
{
   using namespace std;
   auto p = FabriquePlaignard::creer(FabriquePlaignard::Anglais);
   wcout << p->message(Plaignard::MSG_OUTOFMEMORY) << endl;
}

Beaucoup de gens s'arrêtent là. évidemment, la technique d'optimisation par savoir discret peut être appliquée aux instances de Plaignard elles-mêmes, avec un gain en vitesse proportionnel au nombre de messages à supporter :

#include "lexical_cast.h" // mais avec std::wstringstream plutôt que std::stringstream
#include <string>
#include <iostream>
//
// Classe abstraite servant d'interface
//
struct Plaignard
{
   enum MessageId
   {
      MSG_DEFAULT = 0,
      MSG_OUTOFMEMORY, // manque de mémoire
      MSG_IOERROR,     // erreur d'entrée/ sortie
      NB_MESSAGES      // marqueur; doit être à la fin
   };
   using str_type = std::wstring;
   virtual str_type message(const MessageId) const = 0;
   virtual ~Plaignard() = default;
};
//
// Catégories de plaignards
//
class PlaignardFrancais
   : public Plaignard
{
   str_type messages_[NB_MESSAGES];
public:
   PlaignardFrancais()
   {
      messages_[MSG_OUTOFMEMORY] = L"Manque de mémoire";
      messages_[MSG_IOERROR]     = L"Erreur d'entrée/ sortie";
      // etc.
   }
   str_type message(MessageId mid) const
   {
      // le niveau de validation à faire ici dépend du type d'application
      return (mid > NB_MESSAGES)?
        L"Erreur inconnue, code " + lexical_cast<str_type>(mid) :
        messages_[mid];
   }
};
class PlaignardAnglais
   : public Plaignard
{
   str_type messages_[NB_MESSAGES];
public:
   PlaignardAnglais()
   {
      messages_[MSG_OUTOFMEMORY] = L"Out of memory";
      messages_[MSG_IOERROR]     = L"I/O error";
      // etc.
   }
   str_type message(MessageId mid) const
   {
      // le niveau de validation à faire ici dépend du type d'application
      return (mid > NB_MESSAGES)?
        L"Unknown error, code " + lexical_cast<str_type>(mid) :
        messages_[mid];
   }
};
class PlaignardAllemand
   : public Plaignard
{
   str_type messages_[NB_MESSAGES];
public:
   PlaignardAllemand()
   {
      messages_[MSG_OUTOFMEMORY] = L"Aus Gedächtnis heraus";
      messages_[MSG_IOERROR]     = L"Input/Output Störung";
      // etc.
   }
   str_type message(MessageId mid) const
   {
      // le niveau de validation à faire ici dépend du type d'application
      return (mid > NB_MESSAGES)?
        L"unbekannte Störung, Code " + lexical_cast<str_type>(mid) :
        messages_[mid];
   }
};
//
// Fabricant de plaignard
//
#include <memory>
class FabriquePlaignard
{
public:
   enum Categorie
   {
      Francais, Anglais, Allemand,
      Defaut = Francais // doit être le dernier
   };
   static std::unique_ptr<Plaignard> creer(Categorie cat = Defaut)
   {
      using namespace std;
      switch (cat)
      {
      case Francais:
         return make_unique<PlaignardFrancais>();
         break;
      case Anglais:
         return make_unique<PlaignardAnglais>();
         break;
      case Allemand:
         return make_unique<PlaignardAllemand>();
         break;
      default:
        ;
      }
      return nullptr;
   }
};
//
// Petit programme de démonstration
//
int main()
{
   using namespace std;
   auto p = FabriquePlaignard::creer(FabriquePlaignard::Anglais);
   wcout << p->message(Plaignard::MSG_OUTOFMEMORY) << endl;
}

Notez au passage que le programme de test n'a subi aucune modification, ce qui est une excellente nouvelle.

De plus, si chaque Plaignard pouvait être utilisé de manière partagée par plusieurs demandeurs, alors on pourrait simplement tous les créer d'avance (ou en créer un seul de chaque catégorie demandée) et appliquer directement la technique d'optimisation par savoir discret:

#include "lexical_cast.h" // mais avec std::wstringstream plutôt que std::stringstream
#include "incopiable.h"
#include <string>
#include <iostream>
//
// Classe abstraite servant d'interface
//
struct Plaignard
{
   enum MessageId
   {
      MSG_DEFAULT = 0,
      MSG_OUTOFMEMORY, // manque de mémoire
      MSG_IOERROR,     // erreur d'entrée/ sortie
      NB_MESSAGES      // marqueur; doit être à la fin
   };
   using str_type = std::wstring;
   virtual str_type message(MessageId) const = 0;
   virtual ~Plaignard() = default;
};
//
// Catégories de plaignards
//
class PlaignardFrancais
   : public Plaignard
{
   str_type messages_[NB_MESSAGES];
public:
   PlaignardFrancais()
   {
      messages_[MSG_OUTOFMEMORY] = L"Manque de mémoire";
      messages_[MSG_IOERROR]     = L"Erreur d'entrée/ sortie";
      // etc.
   }
   str_type message(MessageId mid) const
   {
      // le niveau de validation à faire ici dépend du type d'application
      return (mid > NB_MESSAGES)?
        L"Erreur inconnue, code " + lexical_cast<str_type>(mid) :
        messages_[mid];
   }
};
class PlaignardAnglais
   : public Plaignard
{
   str_type messages_[NB_MESSAGES];
public:
   PlaignardAnglais()
   {
      messages_[MSG_OUTOFMEMORY] = L"Out of memory";
      messages_[MSG_IOERROR]     = L"I/O error";
      // etc.
   }
   str_type message(MessageId mid) const
   {
      // le niveau de validation à faire ici dépend du type d'application
      return (mid > NB_MESSAGES)?
        L"Unknown error, code " + lexical_cast<str_type>(mid) :
        messages_[mid];
   }
};
class PlaignardAllemand
   : public Plaignard
{
   str_type messages_[NB_MESSAGES];
public:
   PlaignardAllemand()
   {
      messages_[MSG_OUTOFMEMORY] = L"Aus Gedächtnis heraus";
      messages_[MSG_IOERROR]     = L"Input/Output Störung";
      // etc.
   }
   str_type message(const MessageId mid) const
   {
      // le niveau de validation à faire ici dépend du type d'application
      return (mid > NB_MESSAGES)?
        L"unbekannte Störung, Code " + lexical_cast<str_type>(mid) :
        messages_[mid];
   }
};
//
// Fabricant de plaignard
//
#include <memory>
class FabriquePlaignard
   : Incopiable
{
public:
   enum Categorie
   {
      Francais, Anglais, Allemand,
      NB_CATEGORIES, // marqueur
      Defaut = Francais // doit être le dernier
   };
private:
   std::shared_ptr<Plaignard> plaignards_[NB_CATEGORIES];
   FabriquePlaignard()
   {
      using std::make_shared;
      plaignards_[Francais] = make_shared<PlaignardFrancais>();
      plaignards_[Anglais]  = make_shared<PlaignardAnglais>();
      plaignards_[Allemand] = make_shared<PlaignardAllemand>();
      // etc.
   }
public:
   static FabriquePlaignard &get()
   {
      static FabriquePlaignard singleton;
      return singleton;
   }
   std::shared_ptr<Plaignard> creer(Categorie cat = Defaut)
   {
      return (cat > NB_CATEGORIES)? nullptr : plaignards_[cat];
   }
};
//
// Petit programme de démonstration
//
int main()
{
   using namespace std;
   auto p = FabriquePlaignard::get().creer(FabriquePlaignard::Anglais);
   wcout << p->message(Plaignard::MSG_OUTOFMEMORY) << endl;
}

La technique

Nous présumerons donc que nous devons instancier un Plaignard sur demande et que nous souhaitons que l'instanciation soit aussi rapide que possible. La technique ira comme suit :

Concrètement, on obtient alors :

#include "lexical_cast.h" // mais avec std::wstringstream plutôt que std::stringstream
#include "incopiable.h"
#include <string>   // std::string
#include <iostream> // std::wcout, std::endl
//
// Classe abstraite servant d'interface
//
struct Plaignard
{
   enum MessageId
   {
      MSG_DEFAULT = 0,
      MSG_OUTOFMEMORY, // manque de mémoire
      MSG_IOERROR,     // erreur d'entrée/ sortie
      NB_MESSAGES      // marqueur; doit être à la fin
   };
   using str_type = std::wstring;
   virtual str_type message(MessageId) const = 0;
   virtual ~Plaignard() = default;
};
//
// Catégories de plaignards
//
class PlaignardFrancais
   : public Plaignard
{
   str_type messages_[NB_MESSAGES];
public:
   PlaignardFrancais()
   {
      messages_[MSG_OUTOFMEMORY] = L"Manque de mémoire";
      messages_[MSG_IOERROR]     = L"Erreur d'entrée/ sortie";
      // etc.
   }
   str_type message(MessageId mid) const
   {
      // le niveau de validation à faire ici dépend du type d'application
      return (mid > NB_MESSAGES)?
        L"Erreur inconnue, code " + lexical_cast<std::wstring>(mid) :
        messages_[mid];
   }
};
class PlaignardAnglais
   : public Plaignard
{
   str_type messages_[NB_MESSAGES];
public:
   PlaignardAnglais()
   {
      messages_[MSG_OUTOFMEMORY] = L"Out of memory";
      messages_[MSG_IOERROR]     = L"I/O error";
      // etc.
   }
   str_type message(MessageId mid) const
   {
      // le niveau de validation à faire ici dépend du type d'application
      return (mid > NB_MESSAGES)?
        L"Unknown error, code " + lexical_cast<std::wstring>(mid) :
        messages_[mid];
   }
};
class PlaignardAllemand
   : public Plaignard
{
   str_type messages_[NB_MESSAGES];
public:
   PlaignardAllemand()
   {
      messages_[MSG_OUTOFMEMORY] = L"Aus Gedächtnis heraus";
      messages_[MSG_IOERROR]     = L"Input/Output Störung";
      // etc.
   }
   str_type message(MessageId mid) const
   {
      // le niveau de validation à faire ici dépend du type d'application
      return (mid > NB_MESSAGES)?
        L"unbekannte Störung, Code " + lexical_cast<std::wstring>(mid) :
        messages_[mid];
   }
};
//
// Fabricant de plaignard
//
#include <memory>
class FabriquePlaignard
   : Incopiable
{
public:
   enum Categorie
   {
      Francais, Anglais, Allemand,
      NB_CATEGORIES, // marqueur
      Defaut = Francais // doit être le dernier
   };
private:
   struct MicroFabrique
   {
      virtual std::unique_ptr<Plaignard> creer() const = 0;
      virtual ~MicroFabrique() = default;
   };
   struct MicroFabriqueFrancais : MicroFabrique
   {
      std::unique_ptr<Plaignard> creer() const
         { return std::make_unique<PlaignardFrancais>(); }
   };
   struct MicroFabriqueAnglais : MicroFabrique
   {
      std::unique_ptr<Plaignard> creer() const
         { return std::make_unique<PlaignardAnglais>(); }
   };
   struct MicroFabriqueAllemand : MicroFabrique
   {
      std::unique_ptr<Plaignard> creer() const
         { return std::make_unique<PlaignardAllemand>(); }
   };
   std::unique_ptr<MicroFabrique>fabriques_[NB_CATEGORIES];
   FabriquePlaignard()
   {
      using namespace std;
      fabriques_[Francais] = make_unique<MicroFabriqueFrancais>();
      fabriques_[Anglais]  = make_unique<MicroFabriqueAnglais>();
      fabriques_[Allemand] = make_unique<MicroFabriqueAllemand>();
      // etc.
   }
public:
   static FabriquePlaignard &get()
   {
      static FabriquePlaignard singleton;
      return singleton;
   }
   std::unique_ptr<Plaignard> creer(Categorie cat = Defaut)
   {
      return (cat > NB_CATEGORIES)? nullptr : fabriques_[cat]->creer();
   }
};

//
// Petit programme de démonstration
//
int main()
{
   using namespace std;
   auto p = FabriquePlaignard::get().creer(FabriquePlaignard::Anglais);
   wcout << p->message(Plaignard::MSG_OUTOFMEMORY) << endl;
}

En passant par des fabriques individuelles et polymorphiques pour chaque type d'objet à fabriquer, obtient donc :

La vie pourrait être pire...


Valid XHTML 1.0 Transitional

CSS Valide !