Pergunta

Na minha aplicação, existem 10-20 classes que são instanciado uma vez [*]. Aqui está um exemplo:

class SomeOtherManager;

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

Instâncias das classes estão contidas em um objeto:

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

Atualmente theManager usa o new operador, a fim de criar objetos.

A minha intenção é ser capaz de substituir, utilizando plugins, o SomeManagerClass (ou qualquer outra classe) implementação com outro. A fim de substituir a implementação, são necessários 2 passos:

  1. Definir uma classe DerivedSomeManagerClass, que herda SomeManagerClass [Plugin]
  2. Criar a nova classe (DerivedSomeManagerClass) em vez do padrão (SomeManagerClass) [aplicação]

Eu acho que preciso de algum tipo de fábrica de objeto, mas deve ser bastante simples, já que há sempre apenas um tipo para criar (a implementação padrão ou a implementação do usuário).

Qualquer ideia sobre como projetar uma fábrica simples, como acabei de descrever? Considere o fato de que pode haver mais aulas no futuro, por isso deve ser fácil de estender.

[*] Eu não me importo se isso acontecer mais de uma vez.

Editar:. Por favor, note que há mais de dois objetos que estão contidos em theManager

Foi útil?

Solução

Eu acho que existem dois problemas distintos aqui.

Um problema é: como é que theManager nome a classe que tem que criar? Deve manter algum tipo de ponteiro para "uma maneira de criar a classe". soluções possíveis são:

  • manter um ponteiro separado para cada tipo de classe, com uma maneira de defini-lo, mas você já disse que você não gosta deste, uma vez que viola o princípio da DRY
  • manter algum tipo de tabela onde a chave é uma enumeração ou uma string; neste caso, o setter é uma única função com parâmetros (claro se a chave é uma enumeração que você pode usar um vetor em vez de um mapa)

O outro problema é: o que é essa "maneira de criar uma classe"? Infelizmente, não podemos armazenar ponteiros para construtores diretamente, mas nós podemos:

  • criar, como outros já apontaram, uma fábrica para cada classe
  • basta adicionar um estático "criar" função para cada classe; se manter uma assinatura consistente, você pode apenas usar seus ponteiros para funções

Os modelos podem ajudar a evitar a duplicação de código desnecessário em ambos os casos.

Outras dicas

Assumindo uma classe (plugin1) que herda de SomeManagerClass, você precisa de uma hierarquia de classe para construir seus tipos:

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

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

Em seguida, você pode atribuir essas fábricas para um std :: map, onde eles são obrigados a cordas

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

Finalmente seu theManager só precisa saber o nome do plug-in (como string) e pode retornar um objeto do tipo SomeManagerClass com apenas uma linha de código:

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

Editar : Se você não gosta de ter classe de fábrica de um plug-in para cada plugin, você pode modificar o padrão anterior com o seguinte:

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

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

Eu acho que esta é uma solução muito melhor. Além disso, o 'plugin_factory' classe pode adicionar-se ao 'factory_map' se você passar costructor a string.

Eu respondi em outra pergunta SO sobre fábricas C ++. Por favor, consulte se uma fábrica flexível é de interesse. Tento descrever uma maneira antiga de ET ++ para macros de uso que tem funcionado muito bem para mim.

ET ++ foi um projeto para porto velho MacApp para C ++ e X11. No esforço de que Eric Gamma etc começou a pensar em Design Patterns

Eu criar uma fábrica de "base", que tem métodos virtuais para a criação de todos os gestores básicos, e deixe a "meta manager" (theManager na sua pergunta) pegam um ponteiro para a fábrica de base como um parâmetro de construtor.

Estou assumindo que a "fábrica" ??pode personalizar as instâncias de CXYZWManager derivando deles, mas, alternativamente, o construtor de CXYZWManager poderia tomar diferentes argumentos na fábrica "custom".

Um exemplo de código longo que saídas "CSomeManager" e "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;
  }

Aqui está a solução que eu pensava, não é o melhor, mas talvez ele vai ajudar a pensar em soluções melhores:

Para cada classe haveria uma classe criador:

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

Em seguida, os criadores serão reunidos em uma 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;
};

E theManager será criado com thecreator para a criação interna:

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

O problema com esta solução é que ela viola a seco - para cada criador de classe I teria que escrever setter / getter em thecreator

.

Isto parece que seria muito mais simples com a função de templates em oposição a um padrão Abstract Factory

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

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

Se você quiser obtê-los através de uma corda, você pode criar um mapa padrão de strings para ponteiros de função. Aqui está uma implementação 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 : Ao ler as outras respostas eu percebi que isso é muito semelhante ao Dave Van den solução FactorySystem de Eynde, mas estou usando um ponteiro modelo de função em vez de instanciar classes de fábrica templated. Eu acho que a minha solução é um pouco mais leve. Devido às funções estáticas, o único objeto que é instanciado é o próprio mapa. Se você precisar de fábrica para executar outras funções (DestroyManager, etc.), eu acho que sua solução é mais extensível.

Você poderia implementar uma fábrica de objeto com métodos estáticos que retornam uma instância de um gerente-Class. Na fábrica, você pode criar um método para o tipo padrão de gerente e um método para qualquer tipo de gerente que você dá um argumento que representa o tipo do Gerente-classe (dizer com um enum). Este último método deve retornar uma interface em vez de uma classe.

Edit:. Vou tentar dar algum código, mas mente que meus tempos de C ++ são bastante um tempo atrás e eu estou fazendo única Java e alguns scripts de 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;
     }
 };

Agora você deve ser capaz de chamar a Fábrica de via (se você foi capaz de fazer o trabalho exemplo de código):

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

Gostaria de usar modelos como este como eu não posso ver o ponto de aulas 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>;

Você deve dar uma olhada no tutorial em http://downloads.sourceforge.net/papafactory/PapaFactory20080622.pdf?use_mirror= fastbull

Ele contém um grande tutorial sobre a implementação de uma fábrica abstrata em C ++ eo código-fonte que vem com ele é também muito robusto

Chris

Mh eu não entendo cem por cento, e eu não estou realmente em coisas fábrica de livros e artigos.


Se todos os seus gerentes compartilham uma interface similar que poderia derivar de uma classe base, e usar essa classe base no seu programa. Dependendo de onde será feita a decisão qual classe será criado, você tem que usar um identificador para a criação (como dito acima) ou lidar com a decisão que gerente para instanciar internamente.


Outra forma seria a de implementar "políticas" como usando modelos. De modo que você ManagerClass :: create () retorna uma instância específica SomeOtherManagerWhatever. Isso colocaria a decisão que gerente para fazer no código que usa o seu Manager -. Maye este não se destina

ou daquela maneira:


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

(ou algo parecido) Com esta construção pode facilmente usar outros gestores somente mudando a instanciação de MyAwesomeClass.


Também Uma classe para este fim pode ser um pouco por cima. No seu caso uma função fábrica faria eu acho. Bem, é mais uma questão de preferência pessoal.

Se você está pensando em apoiar plugins que estão ligadas de forma dinâmica, o programa terá de fornecer um estábulo ABI (Application Binary Interface), o que significa que você não pode usar C ++ como sua interface principal como C ++ não tem ABI padrão.

Se você quiser plugins para implementar uma interface que define a si mesmo, você terá que fornecer o arquivo de cabeçalho da interface para plugin programador e padronizar em uma interface muito simples C, a fim de criar e excluir o objeto.

Você não pode fornecer uma biblioteca dinâmica que permitirá que você para a classe "novo" plug-in como está. É por isso que você precisa para padronizar uma interface C, a fim de criar o objeto. Usando o objeto C ++ é, então, possível, desde que nenhum dos seus argumentos usar tipos possivelmente incompatíveis, como contêineres STL. Você não será capaz de usar um vetor retornado por outra biblioteca, porque você não pode garantir que a sua implementação STL é a mesma que a sua.

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

Voc no falar sobre theManager. Parece que você quer que controlar qual classe está sendo usado? ou talvez você está tentando cadeia-los juntos?

Parece que você precisa de uma classe base abstrata e um ponteiro para a classe usado atualmente. Se você deseja cadeia você pode fazê-lo em ambos classe abstrata e theManager classe. Se classe abstrata, adicionar um membro para a próxima classe na cadeia, se theManager em seguida, classificá-lo de modo que você que para usar em uma lista. Você vai precisar de uma maneira de adicionar classes para que você vai precisar de uma AddMe () in theManager. Parece que você sabe o que fazer para que w / e que você escolher deve estar certo. Uma lista com um func AddMe é a minha recomendação e se você quiser apenas 1 classe ativa, em seguida, uma função em theManager decidir que seria bom.

Esta talvez mais pesado do que você precisa, mas parece que você está tentando fazer uma classe trabalho de quadro que suporta plugins.

Gostaria de dividi-lo em 3 seções.

1) A classe FrameWork ficaria proprietária dos plugins. Esta classe é responsável por publicar as interfaces fornecidos pelos plugins.

2) um plugin classe possuiria os componets que fazem o trabalho. Esta classe é responsável por registrar as interfaces exportadas, e vinculativa as interfaces importados para os componentes.

3) A terceira seção, os componets são os fornecedores e consumidores das interfaces.

Para tornar as coisas extensível, fazer as coisas em funcionamento pode ser até quebrou em etapas.

  1. Criar tudo.
  2. Fio tudo.
  3. Inicie tudo.

Para quebrar as coisas.

  1. Parar tudo.
  2. Destruir tudo.
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() );
    }
};

Aqui está um mínimo implementação padrão de fábrica que eu vim com em cerca de 15 minutos. Nós usamos um similar que usa mais avançado classes de base.

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

Apenas & colar sobre um aplicativo inicial consola Win32 no VS2005 / 2008. Eu gostaria de salientar uma coisa:

  • Você não precisa criar uma fábrica de concreto para cada classe. Um modelo vai fazer isso por você.
  • Eu gosto de colocar o padrão de fábrica inteira em sua própria classe, de modo que você não precisa se preocupar com a criação de objetos de fábrica e excluí-los. Você simplesmente registrar suas classes, uma classe de fábrica é criado pelo compilador e um objeto de fábrica é criado por padrão. No final de sua vida útil, todas as fábricas são claramente destruída. Eu gosto desta forma de encapsulamento, como não há nenhuma confusão sobre quem governa a vida útil das fábricas.
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top