Question

Dans mon application, 10 à 20 classes 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 l'opérateur new afin de créer des objets.

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

  1. Définit une classe DerivedSomeManagerClass, qui hérite du [plugin] SomeManagerClass
  2. Créez la nouvelle classe (DerivedSomeManagerClass) à la place 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).

Avez-vous une idée de la façon de concevoir une usine simple comme celle que je viens de décrire? Considérez le fait qu'il pourrait y avoir plus de classes dans le futur, il devrait donc être facile de les étendre.

[*] Je me fiche de savoir si cela se produit plus d'une fois.

Modifier : notez que TheManager contient plus de deux objets.

Était-ce utile?

La solution

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

Un problème est le suivant: comment TheManager nomme la classe qu’il doit créer? Il doit garder une sorte de pointeur sur "un moyen de créer la classe". Les solutions possibles sont:

  • conserver 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'aimiez pas cela, car cela violerait le principe DRY
  • conserver une sorte de table où la clé est une énumération ou une chaîne; dans ce cas, le setter est une fonction unique avec des paramètres (bien sûr, si la clé est une énumération, vous pouvez utiliser un vecteur au lieu d’une carte)

L’autre problème est de savoir quelle est cette "manière de créer une classe"? Malheureusement, nous ne pouvons pas stocker les pointeurs vers les constructeurs directement, mais nous pouvons:

  • créer, comme d'autres l'ont souligné, une fabrique pour chaque classe
  • ajoutez simplement un " créer " statique fonction pour chaque classe; s’ils conservent une signature cohérente, vous pouvez simplement utiliser leurs pointeurs sur les fonctions

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

Autres conseils

En supposant qu'une classe (plugin1) 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 fabriques à un std :: map, où elles sont liées à des chaînes

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

Enfin, votre TheManager doit simplement connaître le nom du plug-in (sous forme de chaîne) et peut renvoyer un objet de type SomeManagerClass avec une seule ligne de code:

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

MODIFIER : si vous n'aimez pas avoir une classe de fabriques de plug-in pour chaque plug-in, 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 cette solution est bien meilleure. De plus, la classe 'plugin_factory' pourrait s'ajouter à la 'fabrique_map' si vous transmettez la chaîne à costructor.

J'ai répondu à une autre question SO concernant les usines C ++. Veuillez consulter il si une usine flexible est intéressante. J'essaie de décrire une ancienne méthode utilisée par ET ++ pour utiliser des macros qui fonctionnaient très bien pour moi.

ET ++ était un projet visant à porter l'ancien MacApp en C ++ et X11. Eric Gamma, etc., a commencé à réfléchir à Design Patterns

Je créerais une "base". fabrique qui dispose de méthodes virtuelles pour la création de tous les gestionnaires de base, et laisse le "méta-gestionnaire" (TheManager dans votre question) prend un pointeur sur la fabrique de base en tant que paramètre constructeur.

Je suppose que "l’usine" pouvez personnaliser les instances de CXYZWManager en dérivant à partir d’eux, mais le constructeur de CXYZWManager peut également prendre des arguments différents dans le paramètre & custom; custom " usine.

Un long exemple de code qui génère "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;
  }

Voici la solution à laquelle j'ai pensé, ce n'est pas la meilleure, mais il sera peut-être utile de penser à de meilleures solutions:

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

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

Ensuite, les créateurs seront regroupés dans une 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 enfreint DRY - pour chaque créateur de classe, je devrais écrire setter / getter dans TheCreator.

Cela semble être beaucoup plus simple avec un modèle de fonction, par opposition à un motif Abstract Factory

class ManagerFactory
{
public:
    template <typename T> static BaseManager * getManager() { return new T();}
};

BaseManager * manager1 = ManagerFactory::template getManager<DerivedManager1>();

Si vous souhaitez les obtenir via une chaîne, vous pouvez créer une carte standard à partir de chaînes en pointeurs de fonction. Voici une implémentation qui fonctionne:

#include <map>
#include <string>

class BaseManager
{
public:
    virtual void doSomething() = 0;
};

class DerivedManager1 : public BaseManager
{
public:
    virtual void doSomething() {};
};

class DerivedManager2 : public BaseManager
{
public:
    virtual void doSomething() {};
};

class ManagerFactory
{
public:
    typedef BaseManager * (*GetFunction)();
    typedef std::map<std::wstring, GetFunction> ManagerFunctionMap;
private:
    static ManagerFunctionMap _managers;

public:
    template <typename T> static BaseManager * getManager() { return new T();}
    template <typename T> static void registerManager(const std::wstring& name)
    {
        _managers[name] = ManagerFactory::template getManager<T>;
    }
    static BaseManager * getManagerByName(const std::wstring& name)
    {
        if(_managers.count(name))
        {
            return _managers[name]();
        }
        return NULL;
    }
};
// the static map needs to be initialized outside the class
ManagerFactory::ManagerFunctionMap ManagerFactory::_managers;


int _tmain(int argc, _TCHAR* argv[])
{
    // you can get with the templated function
    BaseManager * manager1 = ManagerFactory::template getManager<DerivedManager1>();
    manager1->doSomething();
    // or by registering with a string
    ManagerFactory::template registerManager<DerivedManager1>(L"Derived1");
    ManagerFactory::template registerManager<DerivedManager2>(L"Derived2");
    // and getting them
    BaseManager * manager2 = ManagerFactory::getManagerByName(L"Derived2");
    manager2->doSomething();
    BaseManager * manager3 = ManagerFactory::getManagerByName(L"Derived1");
    manager3->doSomething();
    return 0;
}

MODIFIER : en lisant les autres réponses, je me suis rendu compte que cela ressemblait beaucoup à la solution FactorySystem de Dave Van den Eynde, mais j'utilise un pointeur de modèle de fonction au lieu d'instancier des classes d'usine modélisées. Je pense que ma solution est un peu plus légère. En raison de fonctions statiques, le seul objet instancié est la carte elle-même. Si vous avez besoin que l’usine exécute d’autres fonctions (DestroyManager, etc.), je pense que sa solution est plus extensible.

Vous pouvez implémenter une fabrique d'objets avec des méthodes statiques qui renvoient une instance de Manager-Class. Dans la fabrique, vous pouvez créer une méthode pour le type de gestionnaire par défaut et une méthode pour tout type de gestionnaire, auxquelles vous donnez un argument représentant le type de la classe de gestionnaire (par exemple, avec une énumération). Cette dernière méthode devrait renvoyer une interface plutôt qu'une classe.

Éditer: Je vais essayer de vous donner du code, mais sachez que mes temps en C ++ sont lointains et que je ne fais que Java et quelques scripts pour le moment.

class Manager { // aka Interface
    public: virtual void someMethod() = 0;
};

class Manager1 : public Manager {
    void someMethod() { return null; }
};

class Manager2 : public Manager {
    void someMethod() { return null; }
};

enum ManagerTypes {
    Manager1, Manager2
};

class ManagerFactory {
    public static Manager* createManager(ManagerTypes type) {
        Manager* result = null;
        switch (type) {
        case Manager1:
             result = new Manager1();
             break;
        case Manager2:
             result = new Manager2();
             break;
        default:
             // Do whatever error logging you want
             break;
        }
        return result;
     }
 };

Vous devriez maintenant pouvoir appeler la fabrique via (si vous avez réussi à faire fonctionner l'exemple de code):

Manager* manager = ManagerFactory.createManager(ManagerTypes.Manager1);

Je voudrais utiliser des modèles comme celui-ci car je ne vois pas le point des classes d'usines:

class SomeOtherManager;

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


class TheBaseManager {
public:
      // 
};

template <class ManagerClassOne, class ManagerClassOther> 
class SpecialManager : public TheBaseManager {
    public:
        virtual ManagerClassOne* someManagerClass() const;
        virtual ManagerClassOther* someOtherManager() const;
};

TheBaseManager* ourManager = new SpecialManager<SomeManagerClass,SomeOtherManager>;

Vous devriez jeter un coup d’œil au tutoriel à fastbull

Il contient un excellent tutoriel sur l'implémentation d'une fabrique abstraite en C ++ et le code source qui l'accompagne est également très robuste

Chris

Mh, je ne comprends pas à cent pour cent, et je ne suis pas vraiment intéressé par les livres d'usine et les articles.

Si tous vos gestionnaires partagent une interface similaire, vous pouvez dériver d'une classe de base et utiliser cette classe de base dans votre programme. Selon l'endroit où la décision sera créée sur la classe à créer, vous devez utiliser un identifiant pour la création (comme indiqué ci-dessus) ou gérer la décision du gestionnaire à instancier en interne.

Une autre solution consisterait à la mettre en œuvre "politique". comme en utilisant des modèles. Pour que ManagerClass :: create () retourne une instance spécifique de SomeOtherManagerW que ce soit. Cela laisserait la décision du responsable à prendre dans le code qui utilise votre Manager - cela n’est pas prévu.

Ou comme ça:

template<class MemoryManagment>
class MyAwesomeClass
{
    MemoryManagment m_memoryManager;
};

(ou quelque chose comme ça) Avec cette construction, vous pouvez facilement utiliser d'autres gestionnaires en modifiant uniquement l'instanciation de MyAwesomeClass.

En outre, une classe à cette fin peut être un peu trop lourde. Dans votre cas, une fonction d'usine ferait l'affaire, je suppose. Eh bien, c’est plus une question de préférence personnelle.

Si vous envisagez de prendre en charge des plug-ins liés dynamiquement, votre programme devra fournir une ABI (interface binaire d'application) stable, ce qui signifie que vous ne pouvez pas utiliser C ++ en tant qu'interface principale, car C ++ ne possède pas d'ABI standard.

Si vous voulez que les plugins implémentent une interface que vous définissez vous-même, vous devrez fournir le fichier d'en-tête de l'interface au plugin programmeur et normaliser une interface C très simple afin de créer et de supprimer l'objet.

Vous ne pouvez pas fournir de bibliothèque dynamique vous permettant de " nouveau " la classe de plugin telle quelle. C'est pourquoi vous devez normaliser une interface C pour créer l'objet. L'utilisation de l'objet C ++ est alors possible tant qu'aucun de vos arguments n'utilise des types potentiellement incompatibles, tels que les conteneurs STL. Vous ne pourrez pas utiliser un vecteur renvoyé par une autre bibliothèque car vous ne pouvez pas vous assurer que leur implémentation STL est identique à la vôtre.

Manager.h

class Manager
{
public:
  virtual void doSomething() = 0;
  virtual int doSomethingElse() = 0;
}

extern "C" {
Manager* newManager();
void deleteManager(Manager*);
}

PluginManager.h

#include "Manager.h"

class PluginManager : public Manager
{
public:
  PluginManager();
  virtual ~PluginManager();

public:
  virtual void doSomething();
  virtual int doSomethingElse();
}

PluginManager.cpp

#include "PluginManager.h"

Manager* newManager()
{
  return new PluginManager();
}
void deleteManager(Manager* pManager)
{
  delete pManager;
}

PluginManager::PluginManager()
{
  // ...
}

PluginManager::~PluginManager()
{
  // ...
}

void PluginManager::doSomething()
{
  // ...
}

int PluginManager::doSomethingElse()
{
  // ...
}

Vous n'avez pas parlé de TheManager. Il semblerait que vous souhaitiez contrôler la classe utilisée. ou peut-être que vous essayez de les chaîner ensemble?

On dirait que vous avez besoin d’une classe de base abstraite et d’un pointeur sur la classe actuellement utilisée. Si vous souhaitez enchaîner, vous pouvez le faire en classe abstraite et en classe de gestionnaire. S'il s'agit d'une classe abstraite, ajoutez un membre à la classe suivante de la chaîne. Si le gestionnaire en question, triez-le afin de l'utiliser dans une liste. Vous aurez besoin d'un moyen d'ajouter des classes, vous aurez donc besoin d'un addMe () dans le gestionnaire. Il semble que vous sachiez ce que vous faites devrait être juste. Une liste avec addMe func est ma recommandation et si vous voulez seulement une classe active, alors une fonction dans TheManager décidant que ce serait bien.

C’est peut-être plus lourd que ce dont vous avez besoin, mais il semble que vous essayez de créer une classe de travail avec cadre prenant en charge les plug-ins.

Je le diviserais en 3 sections.

1) La classe FrameWork serait propriétaire des plugins. Cette classe est responsable de la publication des interfaces fournies par les plugins.

2) Une classe PlugIn posséderait les composants qui font le travail. Cette classe est responsable de l’enregistrement des interfaces exportées et de la liaison des interfaces importées aux composants.

3) La troisième section, les composants sont les fournisseurs et les consommateurs des interfaces.

Pour rendre les choses extensibles, il peut être nécessaire de les mettre en marche.

  1. Tout créer.
  2. Câbler tout.
  3. Commencez tout.

Pour décomposer les choses.

  1. Arrêtez tout.
  2. Détruisez tout.
class IFrameWork {
public:
    virtual ~IFrameWork() {}
    virtual void RegisterInterface( const char*, void* ) = 0;
    virtual void* GetInterface( const char* name ) = 0;
};

class IPlugIn {
public:
    virtual ~IPlugIn() {}
    virtual void BindInterfaces( IFrameWork* frameWork ) {};
    virtual void Start() {};
    virtual void Stop() {};
};

struct SamplePlugin :public IPlugIn {
    ILogger* logger;

    Component1 component1;
    WebServer  webServer;

public:
    SamplePlugin( IFrameWork* frameWork ) 
        :logger( (ILogger*)frameWork->GetInterface( "ILogger" ) ),  //assumes the 'System' plugin exposes this
        component1(),
        webServer( component1 )
    {
        logger->Log( "MyPlugin Ctor()" );

        frameWork->RegisterInterface( "ICustomerManager", dynamic_cast( &component1 ) ); 
        frameWork->RegisterInterface( "IVendorManager", dynamic_cast( &component1 ) ); 
        frameWork->RegisterInterface( "IAccountingManager", dynamic_cast( &webServer ) ); 
    }

    virtual void BindInterfaces( IFrameWork* frameWork ) {
        logger->Log( "MyPlugin BindInterfaces()" );

        IProductManager* productManager( static_cast( frameWork->GetInterface( "IProductManager" ) ) );
        IShippingManager* shippingManager( static_cast( frameWork->GetInterface( "IShippingManager" ) ) );

        component1.BindInterfaces( logger, productManager );
        webServer.BindInterfaces( logger, productManager, shippingManager );
    }

    virtual void Start() {
        logger->Log( "MyPlugin Start()" );

        webServer.Start();
    }

    virtual void Stop() {
        logger->Log( "MyPlugin Stop()" );

        webServer.Stop();
    }
};

class FrameWork :public IFrameWork {
    vector plugIns;
    map interfaces;
public:
    virtual void RegisterInterface( const char* name, void* itfc ) {
        interfaces[ name ] = itfc;
    }
    virtual void* GetInterface( const char* name )  {
        return interfaces[ name ];
    }

    FrameWork() {
        //Only interfaces in 'SystemPlugin' can be used by all methods of the other plugins
        plugIns.push_back( new SystemPlugin( this ) );

        plugIns.push_back( new SamplePlugin( this ) ); 
        //add other plugIns here

        for_each( plugIns.begin(), plugIns.end(), bind2nd( mem_fun( &IPlugIn::BindInterfaces ), this ) );
        for_each( plugIns.begin(), plugIns.end(), mem_fun( &IPlugIn::Start ) );
    }

    ~FrameWork() {
        for_each( plugIns.rbegin(), plugIns.rend(), mem_fun( &IPlugIn::Stop ) );
        for_each( plugIns.rbegin(), plugIns.rend(), Delete() );
    }
};

Voici une implémentation de modèle d'usine minimale que j'ai proposée en 15 minutes environ. Nous en utilisons un qui utilise des classes de base plus avancées.

#include "stdafx.h"
#include <map>
#include <string>

class BaseClass
{
public:
    virtual ~BaseClass() { }
    virtual void Test() = 0;
};

class DerivedClass1 : public BaseClass 
{ 
public:
    virtual void Test() { } // You can put a breakpoint here to test.
};

class DerivedClass2 : public BaseClass 
{ 
public:
    virtual void Test() { } // You can put a breakpoint here to test.
};

class IFactory
{
public:
    virtual BaseClass* CreateNew() const = 0;
};

template <typename T>
class Factory : public IFactory
{
public:
    T* CreateNew() const { return new T(); }
};

class FactorySystem
{
private:
    typedef std::map<std::wstring, IFactory*> FactoryMap;
    FactoryMap m_factories;

public:
    ~FactorySystem()
    {
        FactoryMap::const_iterator map_item = m_factories.begin();
        for (; map_item != m_factories.end(); ++map_item) delete map_item->second;
        m_factories.clear();
    }

    template <typename T>
    void AddFactory(const std::wstring& name)
    {
        delete m_factories[name]; // Delete previous one, if it exists.
        m_factories[name] = new Factory<T>();
    }

    BaseClass* CreateNew(const std::wstring& name) const
    {
        FactoryMap::const_iterator found = m_factories.find(name);
        if (found != m_factories.end())
            return found->second->CreateNew();
        else
            return NULL; // or throw an exception, depending on how you want to handle it.
    }
};

int _tmain(int argc, _TCHAR* argv[])
{
    FactorySystem system;
    system.AddFactory<DerivedClass1>(L"derived1");
    system.AddFactory<DerivedClass2>(L"derived2");

    BaseClass* b1 = system.CreateNew(L"derived1");
    b1->Test();
    delete b1;
    BaseClass* b2 = system.CreateNew(L"derived2");
    b2->Test();
    delete b2;

    return 0;
}

Il suffit de copier & amp; coller sur une application de console Win32 initiale dans VS2005 / 2008. J'aime souligner quelque chose:

  • Vous n'avez pas besoin de créer une usine à béton pour chaque classe. Un modèle le fera pour vous.
  • J'aime placer l'ensemble du modèle de fabrique dans sa propre classe, de sorte que vous n'avez pas à vous soucier de la création ou de la suppression d'objets de fabrique. Vous enregistrez simplement vos classes, une classe fabrique est créée par le compilateur et un objet fabrique est créé par le modèle. À la fin de sa vie, toutes les usines sont proprement détruites. J'aime cette forme d'encapsulation, car il n'y a pas de confusion quant à savoir qui gouverne la durée de vie des usines.
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top