13 votes

Comment concevoir une simple fabrique d'objets C++ ?

Dans mon application, il y a 10-20 classes qui sont instanciées une fois [*]. Voici un exemple :

class SomeOtherManager;

class SomeManagerClass {
public:
    SomeManagerClass(SomeOtherManager*);
    virtual void someMethod1();
    virtual void someMethod2();
};

Les instances des classes sont contenues dans un seul objet :

class TheManager {
public:
    virtual SomeManagerClass* someManagerClass() const;
    virtual SomeOtherManager* someOtherManager() const;
    /** More objects... up to 10-20 */
};

Actuellement, TheManager utilise le nouveau afin de créer des objets.

Mon intention est de pouvoir remplacer, à l'aide de plugins, l'implémentation de SomeManagerClass (ou de toute autre classe) par une autre. Afin de remplacer l'implémentation, 2 étapes sont nécessaires :

  1. Définir une classe DerivedSomeManagerClass, qui hérite de SomeManagerClass [plugin].
  2. Créez la nouvelle classe (DerivedSomeManagerClass) au lieu de la classe par défaut (SomeManagerClass) [application].

Je suppose que j'ai besoin d'une sorte de fabrique d'objets, mais cela devrait être assez simple puisqu'il n'y a toujours qu'un seul type à créer (l'implémentation par défaut ou l'implémentation utilisateur).

Une idée sur la façon de concevoir une usine simple comme celle que je viens de décrire ? Tenez compte du fait qu'il pourrait y avoir d'autres classes à l'avenir, et qu'il devrait donc être facile à étendre.

[*] Je m'en fiche si ça arrive plus d'une fois.

Edit : Veuillez noter que plus de deux objets sont contenus dans TheManager.

19voto

Emiliano Points 2165

En supposant une classe (plugin1) qui hérite de SomeManagerClass, vous avez besoin d'une hiérarchie de classes pour construire vos types :

class factory
{
public:
    virtual SomeManagerClass* create() = 0;
};

class plugin1_factory : public factory
{
public:
    SomeManagerClass* create() { return new plugin1(); }
};

Ensuite, vous pouvez affecter ces usines à un std::map, où elles sont liées à des chaînes de caractères.

std::map<string, factory*>  factory_map;
...
factory_map["plugin1"] = new plugin1_factory();

Enfin, votre TheManager n'a besoin que de connaître le nom du plugin (sous forme de chaîne) et peut retourner un objet de type SomeManagerClass avec une seule ligne de code :

SomeManagerClass* obj = factory_map[plugin_name]->create();

EDIT : Si vous n'aimez pas avoir une classe de fabrique de plugin pour chaque plugin, vous pouvez modifier le modèle précédent avec ceci :

template <class plugin_type>
class plugin_factory : public factory
{
public:
   SomeManagerClass* create() { return new plugin_type(); }
};

factory_map["plugin1"] = new plugin_factory<plugin1>();

Je pense que c'est une bien meilleure solution. De plus, la classe 'plugin_factory' pourrait s'ajouter à la 'factory_map' si vous passez la chaîne au costructeur.

10voto

UncleZeiv Points 9033

Je pense qu'il y a deux problèmes distincts ici.

Un problème se pose : comment TheManager nom la classe qu'il doit créer ? Il doit conserver une sorte de pointeur vers "un moyen de créer la classe". Les solutions possibles sont les suivantes :

  • garder un pointeur séparé pour chaque type de classe, avec un moyen de le définir, mais vous avez déjà dit que vous n'aimez pas cela car cela viole le principe DRY
  • conserver une sorte de tableau dont la clé est un enum ou une chaîne de caractères ; dans ce cas, le setter est une fonction unique avec des paramètres (bien sûr, si la clé est un enum, vous pouvez utiliser un vecteur au lieu d'une map)

L'autre problème est le suivant : quelle est cette "façon de créer une classe" ? Malheureusement, nous ne pouvons pas stocker directement les pointeurs vers les constructeurs, mais nous le pouvons :

  • créer, comme d'autres l'ont souligné, une usine pour chaque classe
  • ajoutez simplement une fonction statique "create" pour chaque classe ; si elles conservent une signature cohérente, vous pouvez simplement utiliser leurs pointeurs vers les fonctions

Les modèles peuvent aider à éviter la duplication inutile du code dans les deux cas.

3voto

epatel Points 32451

J'ai répondu à une autre question de l'OS sur les usines C++. Veuillez consulter si une usine flexible vous intéresse. J'essaie de décrire une ancienne méthode d'ET++ pour utiliser les macros qui a bien fonctionné pour moi.

ET++ était un projet de portage de l'ancienne MacApp vers C++ et X11. Dans le cadre de ce projet, Eric Gamma, etc. a commencé à réfléchir à ce qui suit Modèles de conception

2voto

kshahar Points 3014

Voici la solution à laquelle j'ai pensé, ce n'est pas la meilleure, mais elle aidera peut-être à trouver de meilleures solutions :

Pour chaque classe, il y aurait une classe de créateurs :

class SomeManagerClassCreator {
public:
    virtual SomeManagerClass* create(SomeOtherManager* someOtherManager) { 
        return new SomeManagerClass(someOtherManager); 
    }
};

Ensuite, les créateurs seront réunis dans une même classe :

class SomeManagerClassCreator;
class SomeOtherManagerCreator;

class TheCreator {
public:
    void setSomeManagerClassCreator(SomeManagerClassCreator*);
    SomeManagerClassCreator* someManagerClassCreator() const;

    void setSomeOtherManagerCreator(SomeOtherManagerCreator*);
    SomeOtherManagerCreator* someOtherManagerCreator() const;
private:
    SomeManagerClassCreator* m_someManagerClassCreator;
    SomeOtherManagerCreator* m_someOtherManagerCreator;
};

Et TheManager sera créé avec TheCreator pour la création interne :

class TheManager {
public:
    TheManager(TheCreator*);
    /* Rest of code from above */
};

Le problème avec cette solution est qu'elle viole le DRY - pour chaque créateur de classe, je devrais écrire un setter/getter dans TheCreator.

2voto

Joris Timmermans Points 8075

Je créerais une fabrique "de base" qui possède des méthodes virtuelles pour la création de tous les gestionnaires de base, et je laisserais le "méta-gestionnaire" (TheManager dans votre question) prendre un pointeur vers la fabrique de base comme paramètre de construction.

Je suppose que la "fabrique" peut personnaliser les instances de CXYZWManager en dérivant d'elles, mais le constructeur de CXYZWManager pourrait aussi prendre des arguments différents dans la fabrique "personnalisée".

Un long exemple de code qui produit "CSomeManager" et "CDerivedFromSomeManager" :

#include <iostream>
//--------------------------------------------------------------------------------
class CSomeManager
  {
  public:
    virtual const char * ShoutOut() { return "CSomeManager";}
  };

//--------------------------------------------------------------------------------
class COtherManager
  {
  };

//--------------------------------------------------------------------------------
class TheManagerFactory
  {
  public:
    // Non-static, non-const to allow polymorphism-abuse
    virtual CSomeManager   *CreateSomeManager() { return new CSomeManager(); }
    virtual COtherManager  *CreateOtherManager() { return new COtherManager(); }
  };

//--------------------------------------------------------------------------------
class CDerivedFromSomeManager : public CSomeManager
  {
  public:
    virtual const char * ShoutOut() { return "CDerivedFromSomeManager";}
  };

//--------------------------------------------------------------------------------
class TheCustomManagerFactory : public TheManagerFactory
  {
  public:
    virtual CDerivedFromSomeManager        *CreateSomeManager() { return new CDerivedFromSomeManager(); }

  };

//--------------------------------------------------------------------------------
class CMetaManager
  {
  public:
    CMetaManager(TheManagerFactory *ip_factory)
      : mp_some_manager(ip_factory->CreateSomeManager()),
        mp_other_manager(ip_factory->CreateOtherManager())
      {}

    CSomeManager  *GetSomeManager()  { return mp_some_manager; }
    COtherManager *GetOtherManager() { return mp_other_manager; }

  private:
    CSomeManager  *mp_some_manager;
    COtherManager *mp_other_manager;
  };

//--------------------------------------------------------------------------------
int _tmain(int argc, _TCHAR* argv[])
  {
  TheManagerFactory standard_factory;
  TheCustomManagerFactory custom_factory;

  CMetaManager meta_manager_1(&standard_factory);
  CMetaManager meta_manager_2(&custom_factory);

  std::cout << meta_manager_1.GetSomeManager()->ShoutOut() << "\n";
  std::cout << meta_manager_2.GetSomeManager()->ShoutOut() << "\n";
  return 0;
  }

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X