Pregunta

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 utiliza el operador nuevo para crear objetos.

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

  1. Definir 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 la que 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 extender.

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

Editar: Tenga en cuenta que hay más de dos objetos contenidos en TheManager.

¿Fue útil?

Solución

Creo que hay dos problemas separados aquí.

Un problema es: ¿cómo TheManager nombra la clase que tiene que crear? Debe mantener algún tipo de puntero a "una forma de crear la clase". Las posibles soluciones son:

  • mantener un puntero separado para cada tipo de clase, con una forma de configurarlo, pero ya dijiste que no te gusta, ya que viola el principio DRY
  • mantener algún tipo de tabla donde la clave es una enumeración o una cadena; en este caso, el setter es una función única con parámetros (por supuesto, si la clave es una enumeración, puede usar un vector en lugar de un mapa)

El otro problema es: ¿cuál es esta " forma de crear una clase " ;? Lamentablemente, no podemos almacenar punteros a los constructores directamente, pero podemos:

  • crear, como otros han señalado, una fábrica para cada clase
  • simplemente agregue una estática " create " función para cada clase; si mantienen una firma coherente, puede usar sus punteros para funciones

Las plantillas pueden ayudar a evitar la duplicación innecesaria de código en ambos casos.

Otros consejos

Suponiendo una clase (plugin1) que hereda de SomeManagerClass, necesita una jerarquía de clases para construir sus tipos:

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

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

Entonces puede asignar esas fábricas a un std :: map, donde están unidas 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 complementos 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 costructor la cadena.

He respondido en otra pregunta SO sobre las fábricas de C ++. Consulte allí si una fábrica flexible es de interés. Intento describir una forma antigua de ET ++ para usar macros que me ha funcionado muy bien.

ET ++ fue un proyecto para portar MacApp antiguo a C ++ y X11. En su esfuerzo, Eric Gamma, etc., comenzó a pensar en Patrones de diseño

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

Asumo 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 " custom " fábrica.

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

Aquí está la solución que pensé, no es la mejor, pero tal vez me ayude a pensar en mejores soluciones:

Para cada clase habría una clase de creador:

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

Luego, los creadores se reunirán en una clase:

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

Y TheManager se creará con TheCreator para la creación interna:

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

El problema con esta solución es que viola DRY: para cada creador de clase, tendría que escribir setter / getter en TheCreator.

Parece que sería mucho más simple con la función de plantilla en lugar de 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 a punteros de 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 instanciar 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 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.

Puede implementar una fábrica de objetos con métodos estáticos que devuelven una instancia de una clase de administrador. En la fábrica, puede crear un método para el tipo de administrador predeterminado y un método para cualquier tipo de administrador, al que le da un argumento que representa el tipo de la clase de administrador (digamos con una enumeración). Este último método debería devolver una interfaz en lugar de una clase.

Editar: Trataré de dar algo de código, pero tenga en cuenta que mis tiempos de C ++ han pasado bastante tiempo y que solo estoy haciendo Java y algunas secuencias de comandos por el momento.

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

Ahora debería poder llamar a Factory a través de (si ha podido hacer que el ejemplo de código funcione):

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

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

Deberías echar un vistazo al tutorial en http://downloads.sourceforge.net/papafactory/PapaFactory20080622.pdf?use_mirror= fastbull

Contiene un excelente tutorial sobre la implementación de una fábrica Abstract en C ++ y el código fuente que viene con él también es muy robusto

Chris

Mh, no entiendo al cien por cien, y no estoy realmente interesado en cosas de fábrica de libros y artículos.


Si todos sus gerentes comparten una interfaz similar, 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 instanciará internamente.


Otra forma sería implementarla "política" como mediante el uso de plantillas. Para que You ManagerClass :: create () devuelva una instancia específica de SomeOtherManagerWhatever. Esto establecería la decisión de qué administrador tomar en el código que usa su Administrador - Maye, esto no es lo que se pretende.

O de esa manera:


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

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


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

Si planea admitir complementos que están dinámicamente vinculados, su programa necesitará proporcionar una ABI estable (interfaz binaria de aplicación), eso significa que no puede usar C ++ como su interfaz principal ya que C ++ no tiene ABI estándar.

Si desea que los complementos implementen una interfaz que usted mismo defina, deberá proporcionar el archivo de encabezado de la interfaz al programador de complementos y estandarizar en una interfaz C muy simple para crear y eliminar el objeto.

No puede proporcionar una biblioteca dinámica que le permita " nueva " la clase de complemento tal cual. Es por eso que necesita estandarizar en una interfaz C para crear el objeto. Es posible usar el objeto C ++ siempre que ninguno de sus argumentos use tipos posiblemente incompatibles, como los contenedores STL. No podrá usar un vector devuelto por otra biblioteca, porque no puede asegurarse de que su implementación STL sea la misma que la suya.

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()
{
  // ...
}

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

Parece que necesita una clase base abstracta y un puntero a la clase utilizada actualmente. Si desea encadenar, puede hacerlo tanto en la clase abstracta como en la clase gerente. Si es una clase abstracta, agregue un miembro a la siguiente clase en cadena, si es el gerente, ordénelo para que lo use 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 con lo que elijas debería ser lo correcto. Una lista con una función addMe es mi recomendación y si desea solo 1 clase activa, una función en TheManager decidirá que sería buena.

Esto puede ser más pesado de lo que necesita, pero parece que está intentando crear una clase de trabajo de marco que admita complementos.

Lo dividiría en 3 secciones.

1) La clase FrameWork sería propietaria de los complementos. Esta clase es responsable de publicar las interfaces proporcionadas por los complementos.

2) Una clase PlugIn sería propietaria de los componentes que hacen el trabajo. Esta clase es responsable de registrar las interfaces exportadas y vincular las interfaces importadas a los componentes.

3) La tercera sección, los componentes son los proveedores y consumidores de las interfaces.

Para hacer que las cosas sean extensibles, ponerlas en funcionamiento podría dividirse en etapas.

  1. Crea todo.
  2. Conecta todo.
  3. Comienza todo.

Para descomponer las cosas.

  1. Detener todo.
  2. Destruye todo.
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() );
    }
};

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

Solo copia & amp; pegue sobre una aplicación de consola Win32 inicial en VS2005 / 2008. Me gusta señalar algo:

  • No es necesario crear una fábrica de concreto para cada clase. Una plantilla lo hará por usted.
  • Me gusta colocar 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 tus 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 útil de las fábricas.
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top