[c++] ¿Cómo diseñar una fábrica simple de objetos en C ++?


6 Answers

Asumiendo una clase (plugin1) que hereda de SomeManagerClass, necesitas una jerarquía de clases para construir tus tipos:

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

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

Luego puede asignar esas fábricas a std :: map, donde están enlazadas a cadenas

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

Finalmente, su TheManager solo necesita saber el nombre del complemento (como cadena) y puede devolver un objeto de tipo SomeManagerClass con solo una línea de código:

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

EDITAR : si no desea tener una clase de fábrica de complemento para cada complemento, puede modificar el patrón anterior con esto:

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

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

Creo que esta es una solución mucho mejor. Además, la clase 'plugin_factory' podría agregarse al 'factory_map' si pasa a costructor la cadena.

Question

En mi aplicación, hay 10-20 clases que se instancian una vez [*]. Aquí hay un ejemplo:

class SomeOtherManager;

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

Las instancias de las clases están contenidas en un objeto:

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

Actualmente TheManager usa el nuevo operador para crear objetos.

Mi intención es poder reemplazar, usando complementos, la implementación SomeManagerClass (o cualquier otra clase) con otra. Para reemplazar la implementación, se necesitan 2 pasos:

  1. Defina una clase DerivedSomeManagerClass, que hereda SomeManagerClass [plugin]
  2. Cree la nueva clase (DerivedSomeManagerClass) en lugar de la predeterminada (SomeManagerClass) [aplicación]

Supongo que necesito algún tipo de fábrica de objetos, pero debería ser bastante simple ya que siempre hay un solo tipo para crear (la implementación predeterminada o la implementación del usuario).

¿Alguna idea sobre cómo diseñar una fábrica simple como acabo de describir? Considere el hecho de que podría haber más clases en el futuro, por lo que debería ser fácil de ampliar.

[*] No me importa si sucede más de una vez.

Editar: tenga en cuenta que hay más de dos objetos que están contenidos en TheManager.




Usaría plantillas como esta ya que no puedo ver el punto de las clases de fábricas:

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>;



No hablaste de TheManager. Parece que quieres que eso controle qué clase se está utilizando? o tal vez tratando de encadenarlos?

Parece que necesita una clase base abstracta y un puntero a la clase actualmente utilizada. Si desea encadenar, puede hacerlo tanto en la clase abstracta como en la clase administrativa. Si es clase abstracta, agregue un miembro a la siguiente clase en la cadena, si el administrador lo ordena para que pueda usarlo en una lista. Necesitará una forma de agregar clases, por lo que necesitará un addMe () en el administrador. Parece que sabes lo que estás haciendo para que, si eliges, sea el correcto. Una lista con un func addMe es mi recomendación y si solo quieres 1 clase activa, entonces una función en TheManager decide que sería buena.




Parece que sería mucho más simple con plantillas de función en comparación con un patrón Abstract Factory

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

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

Si desea obtenerlos a través de una cadena, puede crear un mapa estándar de cadenas para punteros a la función. Aquí hay una implementación que funciona:

#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;
}

EDITAR : Al leer las otras respuestas me di cuenta de que esto es muy similar a la solución FactorySystem de Dave Van den Eynde, pero estoy usando un puntero de plantilla de función en lugar de crear instancias de clases de fábrica con plantilla. Creo que mi solución es un poco más ligera. Debido a las funciones estáticas, el único objeto que se crea una instancia es el mapa en sí. Si necesita que la fábrica realice otras funciones (DestroyManager, etc.), creo que su solución es más extensible.




Mh. No entiendo al cien por cien, y no me gustan las cosas de fábrica de libros y artículos.

Si todos sus gerentes comparten una interfaz similar, usted podría derivar de una clase base y usar esta clase base en su programa. Dependiendo de dónde se tomará la decisión de qué clase se creará, debe usar un identificador para la creación (como se indicó anteriormente) o manejar la decisión de qué administrador crear una instancia internamente.

Otra forma sería implementar su "política", como mediante el uso de plantillas. Para que You ManagerClass :: create () devuelva una instancia específica SomeOtherManagerWhatever. Esto tomaría la decisión que el administrador debe hacer en el código que usa su Administrador - Maye no es la intención.

O de esa manera:

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

(o algo por el estilo) Con esta construcción puede usar fácilmente otros administradores solo cambiando la creación de instancias de MyAwesomeClass.

También una clase para este propósito podría ser un poco exagerada. En tu caso, supongo que una función de fábrica. Bueno, es más una cuestión de preferencia personal.




Aquí hay una implementación mínima de patrón de fábrica que se me ocurrió en unos 15 minutos. Usamos uno similar que usa clases base más avanzadas.

#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;
}

Simplemente copie y pegue sobre una aplicación de consola Win32 inicial en VS2005 / 2008. Me gustaría señalar algo:

  • No es necesario crear una fábrica de concreto para cada clase. Una plantilla lo hará por usted.
  • Me gusta ubicar todo el patrón de fábrica en su propia clase, para que no tenga que preocuparse por crear objetos de fábrica y eliminarlos. Simplemente registra sus clases, el compilador crea una clase de fábrica y el patrón crea un objeto de fábrica. Al final de su vida útil, todas las fábricas se destruyen limpiamente. Me gusta esta forma de encapsulación, ya que no hay confusión sobre quién gobierna la vida de las fábricas.



Creé una fábrica "base" que tiene métodos virtuales para la creación de todos los administradores básicos, y dejo que el "meta manager" (TheManager en su pregunta) tome un puntero a la fábrica base como parámetro de constructor.

Supongo que la "fábrica" ​​puede personalizar las instancias de CXYZWManager derivando de ellas, pero alternativamente, el constructor de CXYZWManager podría tomar diferentes argumentos en la fábrica "personalizada".

Un ejemplo de código extenso que genera "CSomeManager" y "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;
  }



Related