Подтвердить что ты не робот

Как создать простой объект С++ factory?

В моем приложении есть 10-20 классов, которые создаются один раз [*]. Вот пример:

class SomeOtherManager;

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

Экземпляры классов содержатся в одном объекте:

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

В настоящее время TheManager использует новый оператор для создания объектов.

Мое намерение состоит в том, чтобы заменить, используя плагины, реализацию SomeManagerClass (или любого другого класса) с другой. Чтобы заменить реализацию, необходимо выполнить 2 шага:

  • Определить класс DerivedSomeManagerClass, который наследует SomeManagerClass [плагин]
  • Создайте новый класс (DerivedSomeManagerClass) вместо стандартного (SomeManagerClass) [application]

Я предполагаю, что мне нужен какой-то объект factory, но он должен быть довольно простым, так как всегда существует только один тип для создания (реализация по умолчанию или реализация пользователя).

Любая идея о том, как создать простой factory, как я только что описал? Рассмотрите тот факт, что в будущем может быть больше классов, поэтому его легко расширить.

[*] Мне все равно, если это происходит более одного раза.

Изменить: Обратите внимание, что в TheManager содержится более двух объектов.

4b9b3361

Ответ 1

Я думаю, что здесь есть две отдельные проблемы.

Одна из проблем: как TheManager называет класс, который он должен создать? Он должен содержать какой-то указатель на "способ создания класса". Возможные решения:

  • сохранение отдельного указателя для каждого типа класса с возможностью его установки, но вы уже сказали, что вам это не нравится, поскольку он нарушает принцип DRY
  • сохранение какой-либо таблицы, где ключ представляет собой перечисление или строку; в этом случае сеттер является единственной функцией с параметрами (конечно, если ключ является перечислением, вы можете использовать вектор вместо карты).

Другая проблема: что это за "способ создания класса"? К сожалению, мы не можем напрямую указывать указатели на конструкторы, но мы можем:

  • создайте, как указывали другие, factory для каждого класса
  • просто добавьте статическую функцию "create" для каждого класса; если они сохраняют согласованную подпись, вы можете просто использовать свои указатели для функций

Шаблоны могут помочь избежать ненужного дублирования кода в обоих случаях.

Ответ 2

Предполагая, что класс (plugin1), который наследуется от SomeManagerClass, вам нужна иерархия классов для создания ваших типов:

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

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

Затем вы можете назначить эти фабрики на std:: map, где они привязаны к строкам

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

Наконец, ваш TheManager просто должен знать имя плагина (как строку) и может возвращать объект типа SomeManagerClass только с одной строкой кода:

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

EDIT. Если вам не нравится иметь один плагин factory для каждого плагина, вы можете изменить предыдущий шаблон следующим образом:

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

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

Я думаю, что это гораздо лучшее решение. Кроме того, класс "plugin_factory" может добавить себя к "factory_map", если вы передадите стоимость структуры строки.

Ответ 3

Я ответил в другом вопросе о С++ фабриках. См. там, если интересен гибкий factory. Я пытаюсь описать старый путь из ET ++, чтобы использовать макросы, которые отлично поработали для меня.

ET ++ был проектом для переноса старого MacApp на С++ и X11. В попытке этого Эрик Гамма и т.д. Начал думать о Design Patterns

Ответ 4

Здесь решение, о котором я думал, это не лучший, но, возможно, это поможет подумать о лучших решениях:

Для каждого класса будет класс создателя:

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

Затем создатели будут собраны в одном классе:

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

И TheManager будет создан с помощью TheCreator для внутреннего создания:

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

Проблема с этим решением заключается в том, что он нарушает DRY - для каждого создателя класса мне придется писать setter/getter в TheCreator.

Ответ 5

Я бы создал "базу" factory, у которой есть виртуальные методы для создания всех основных менеджеров, и пусть "мета-менеджер" (TheManager в вашем вопросе) возьмет указатель на базу factory как конструктор.

Я предполагаю, что "factory" может настраивать экземпляры CXYZWManager, исходя из них, но в качестве альтернативы конструктор CXYZWManager может принимать разные аргументы в "пользовательском" factory.

Пример длинного кода, который выводит "CSomeManager" и "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;
  }

Ответ 6

Похоже, это было бы намного проще с шаблоном функций, а не с шаблоном Abstract Factory

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

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

Если вы хотите получить их через строку, вы можете создать стандартную карту из строк в указатели на функции. Вот реализация, которая работает:

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

EDIT. При чтении других ответов я понял, что это очень похоже на решение Dave Van den Eynde FactorySystem, но я использую указатель шаблона функции вместо создания шаблонных классов Factory. Я думаю, что мое решение немного более легкое. Из-за статических функций единственным объектом, который получает экземпляр, является сама карта. Если вам понадобится Factory для выполнения других функций (DestroyManager и т.д.), Я думаю, что его решение более расширяемо.

Ответ 7

Вы можете реализовать объект factory со статическими методами, которые возвращают экземпляр класса-менеджера. В factory вы можете создать метод для типа диспетчера по умолчанию и метод для любого типа менеджера, которому вы даете аргумент, представляющий тип класса Manager (например, с перечислением). Этот последний метод должен возвращать интерфейс, а не класс.

Изменить: я попытаюсь дать некоторый код, но помните, что мои С++-времена довольно давно вернулись, и пока я делаю только Java и некоторые скрипты.

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

Теперь вы можете позвонить через factory через (если вам удалось создать образец кода):

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

Ответ 8

Я бы использовал шаблоны, подобные этому, так как я не вижу точки заводов:

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

Ответ 10

Mh Я не понимаю на сто процентов, и я не нахожусь в factory материалах из книг и статей.


Если все ваши менеджеры используют аналогичный интерфейс, вы можете получить базовый класс и использовать этот базовый класс в своей программе. В зависимости от того, где будет создано решение, какой класс будет создан, вы должны использовать идентификатор для создания (как указано выше) или обрабатывать решение, которое менеджер должен создать внутри себя.


Другим способом было бы реализовать его "политику", например, с помощью шаблонов. Так что вы ManagerClass:: create() возвращают определенный экземпляр SomeOtherManagerWhatever. Это заложило бы решение, которое менеджер должен сделать в коде, который использует ваш менеджер - Maye, это не предназначено.

Или так:


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

(или что-то в этом роде) С помощью этой конструкции вы можете легко использовать других менеджеров, только изменяя экземпляр MyAwesomeClass.


Также класс для этой цели может быть немного выше вершины. В вашем случае функция factory, я думаю. Ну, это скорее вопрос личных предпочтений.

Ответ 11

Если вы планируете поддерживать динамически связанные плагины, ваша программа должна будет обеспечить стабильный ABI (Application Binary Interface), что означает, что вы не можете использовать С++ в качестве основного интерфейса, поскольку С++ не имеет стандартного ABI.

Если вы хотите, чтобы плагины реализовали интерфейс, который вы сами определяете, вам нужно будет предоставить файл заголовка интерфейса программисту плагинов и стандартизировать на очень простом C-интерфейсе, чтобы создать и удалить объект.

Вы не можете предоставить динамическую библиотеку, которая позволит вам "новый" класс плагина как есть. Вот почему вам нужно стандартизировать интерфейс C для создания объекта. Использование С++-объекта тогда возможно, если ни один из ваших аргументов не использует, возможно, несовместимые типы, такие как контейнеры STL. Вы не сможете использовать вектор, возвращенный другой библиотекой, потому что вы не можете гарантировать, что их реализация STL такая же, как ваша.

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

Ответ 12

Ты не говорил о TheManager. Похоже, вы хотите, чтобы контролировать, какой класс используется? или, может быть, вы пытаетесь связать их вместе?

Похоже, вам нужен абстрактный базовый класс и указатель на текущий класс. Если вы хотите связать себя, вы можете сделать это как в классе abstract, так и в классе themanager. Если абстрактный класс, добавьте член в следующий класс в цепочке, если themanager затем сортирует его, чтобы вы использовали его в списке. Вам понадобится способ добавления классов, поэтому вам понадобится addMe() в themanager. Похоже, вы знаете, что вы делаете так, чтобы вы выбрали правильный выбор. Список с addMe func - моя рекомендация, и если вы хотите только 1 активный класс, тогда функция в TheManager, которая решит, что это будет хорошо.

Ответ 13

Это может быть тяжелее, чем вам нужно, но похоже, что вы пытаетесь создать класс работы фрейма, который поддерживает плагины.

Я разбил бы его на 3 раздела.

1) Класс FrameWork будет иметь плагины. Этот класс отвечает за публикации интерфейсов, предоставляемых плагинами.

2) Класс PlugIn будет владеть компонентами, которые выполняют работу. Этот класс отвечает за регистрацию экспортированных интерфейсов и привязывает импортированные интерфейсы к компонентам.

3) Третий раздел, компоненты - поставщики и потребители интерфейсов.

Чтобы сделать вещи растяжимыми, все встает и работает, возможно, разбивается на этапы.

  • Создайте все.
  • Подключите все.
  • Начать все.

Чтобы сломать вещи.

  • Остановить все.
  • Уничтожьте все.
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() );
    }
};

Ответ 14

Здесь минимальная реализация шаблона factory, с которой я столкнулся примерно через 15 минут. Мы используем аналогичный, который использует более сложные базовые классы.

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

Просто скопируйте и вставьте поверх первоначального консольного приложения Win32 в VS2005/2008. Мне нравится указывать что-то:

  • Вам не нужно создавать конкретный factory для каждого класса. Шаблон сделает это для вас.
  • Мне нравится размещать весь шаблон factory в своем собственном классе, так что вам не нужно беспокоиться о создании объектов factory и удалении их. Вы просто регистрируете свои классы, класс factory создается компилятором, а объект factory создается шаблоном. В конце своей жизни все заводы полностью уничтожены. Мне нравится эта форма инкапсуляции, так как нет путаницы в отношении того, кто управляет жизнью заводов.