Как создать простую фабрику объектов C++?
в моем приложении, есть 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]
Я думаю, мне нужна какая-то фабрика объектов, но она должна быть довольно простой, так как всегда есть только один тип для создания (реализация по умолчанию или реализация пользователя).
любая идея о том, как спроектировать простой фабрика, как я только что описал? Рассмотрим тот факт, что в будущем может быть больше классов, поэтому его легко расширить.
[*] мне все равно, если это произойдет больше одного раза.
Edit: обратите внимание, что в TheManager содержится более двух объектов.
14 ответов
Я думаю, что есть две проблемы.
одна проблема: как TheManager имя класс, который он должен создать? Он должен содержать какой-то указатель на "способ создания класса". Возможные решения:
- сохранение отдельного указателя для каждого вида класса с возможностью его установки, но вы уже сказали, что вам это не нравится, поскольку это нарушает принцип DRY
- сохранение какой-то таблицы, где ключ является перечислением или строка; в этом случае сеттер-Это одна функция с параметрами (конечно, если ключ является перечислением, вы можете использовать вектор вместо карты)
другая проблема: что это за "способ создания класса"? К сожалению, мы не можем хранить указатели на конструкторы напрямую, но мы можем:
- создайте, как указывали другие, фабрику для каждого класса
- просто добавьте статическую функцию "create" для каждого класса; если они сохраняют согласованность подпись, вы можете просто использовать указатели на функции
"Шаблоны" смогут помочь избежать ненужного дублирования кода в обоих случаях.
предполагая класс (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();
редактировать: Если вам не нравится plugin factory class для каждого плагина вы можете изменить предыдущий шаблон следующим образом:
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', если вы передадите costructor строку.
Я ответил на другой вопрос SO о фабриках C++. Пожалуйста, смотрите здесь Если гибкая фабрика представляет интерес. Я пытаюсь описать старый способ из ET++ для использования макросов, который отлично работал для меня.
ET++ был проектом для порта старого MacApp на C++ и X11. В усилии его Эрик гамма etc начал думать о Шаблоны Проектирования
Я бы создал" базовую " фабрику, которая имеет виртуальные методы для создания всех базовых менеджеров, и пусть "meta manager" (TheManager в вашем вопросе) берет указатель на базовую фабрику в качестве параметра конструктора.
Я предполагаю, что" фабрика "может настраивать экземпляры CXYZWManager, производя от них, но в качестве альтернативы конструктор CXYZWManager может принимать разные аргументы в" пользовательской " фабрике.
длинный пример кода, который выводит "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;
}
вот решение, о котором я думал, это не лучший, но, возможно, это поможет придумать лучшие решения:
для каждого класса будет класс создателя:
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 */
};
проблема с этим решением заключается в том, что он нарушает сухой-для каждого создателя класса мне придется писать сеттер/геттер в TheCreator.
это похоже на то, что было бы намного проще с шаблоном функций, а не абстрактным заводским шаблоном
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;
}
редактировать: читая другие ответы, я понял, что это очень похоже на решение FactorySystem Дэйва Ван ден Эйнде, но я использую указатель шаблона функции вместо создания экземпляров шаблонных фабричных классов. Я думаю, что мое решение немного более легкое. Из-за статических функций единственным объектом, который получает экземпляр, является сама карта. Если вам нужна фабрика для выполнения других функций (DestroyManager и т. д.), Я думаю, что его решение более расширяемо.
вы можете реализовать фабрику объектов со статическими методами, возвращающими экземпляр класса Manager. На фабрике вы можете создать метод для типа менеджера по умолчанию и метод для любого типа менеджера, который вы даете аргумент, представляющий тип класса Manager (скажем, с перечислением). Этот последний метод должен возвращать интерфейс, а не класс.
Edit: я попытаюсь дать некоторый код, но имейте в виду, что мои времена c++ довольно давно, и я делаю только 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;
}
};
теперь вы должны иметь возможность вызвать фабрику через (Если вы смогли сделать образец кода работать):
Manager* manager = ManagerFactory.createManager(ManagerTypes.Manager1);
Я бы использовал такие шаблоны, поскольку я не вижу смысла классов фабрик:
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>;
вы должны взглянуть на учебник по http://downloads.sourceforge.net/papafactory/PapaFactory20080622.pdf?use_mirror=fastbull
Он содержит отличный учебник по реализации абстрактной фабрики на C++ , и исходный код, который поставляется с ним, также очень надежен
Крис
Если все ваши менеджеры имеют одинаковый интерфейс, вы можете получить его из базового класса и использовать этот базовый класс в своей программе. В зависимости от того, где будет принято решение, какой класс будет создан, вы должны использовать идентификатор для создания (как указано выше) или обрабатывать решение, какой менеджер создать экземпляр внутри.
другой способом было бы реализовать его "политику", например, с помощью шаблонов. Так что вы ManagerClass:: create() возвращает определенный экземпляр SomeOtherManagerWhatever. Это заложило бы решение, которое менеджер должен сделать в коде, который использует ваш менеджер-Maye это не предназначено.
или так:
template<class MemoryManagment>
class MyAwesomeClass
{
MemoryManagment m_memoryManager;
};
(или что-то подобное) С помощью этой конструкции вы можете легко использовать другие менеджеры, изменяя только экземпляр MyAwesomeClass.
Также A класс для этой цели может быть немного перегружен. В вашем случае, я думаю, подойдет Фабричная функция. Ну, это скорее вопрос личных предпочтений.
Если вы планируете поддерживать плагины, которые динамически связаны, ваша программа должна будет обеспечить стабильный ABI (двоичный интерфейс приложения), это означает, что вы не можете использовать C++ в качестве основного интерфейса, поскольку C++ не имеет стандартного ABI.
Если вы хотите, чтобы Плагины реализовали интерфейс, который вы определяете сами, вам придется предоставить файл заголовка интерфейса программисту плагинов и стандартизировать очень простой интерфейс C, чтобы создать и удалить объект.
вы не можете предоставить динамическую библиотеку, которая позволит вам "создать" класс плагина как есть. Вот почему вам нужно стандартизировать интерфейс C для создания объекта. Использование объекта C++ возможно до тех пор, пока ни один из ваших аргументов не использует несовместимые типы, такие как контейнеры STL. Вы не сможете использовать вектор, возвращаемый другой библиотекой, потому что вы не можете гарантировать, что их реализация STL совпадает с твой.
диспетчер.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()
{
// ...
}
вы не говорили о TheManager. Похоже, вы хотите, чтобы это контролировало, какой класс используется? или ты пытаешься связать их вместе?
похоже, вам нужен абстрактный базовый класс и указатель на текущий используемый класс. Если вы хотите создать цепочку, вы можете сделать это как в абстрактном классе, так и в классе themanager. Если абстрактный класс, добавьте член в следующий класс в цепочке, если themanager затем отсортировать его в порядке, который использовать в списке. Вам понадобится способ добавить классы так вам понадобится addMe () в themanager. Похоже, вы знаете, что делаете, чтобы вы выбрали правильно. Список с addMe func-это моя рекомендация, и если вы хотите только 1 активный класс, то функция в TheManager решит, что это будет хорошо.
это может быть тяжелее, чем вам нужно, но похоже, что вы пытаетесь сделать рабочий класс фрейма, который поддерживает плагины.
Я бы разбил его на 3 секции.
1) класс FrameWork будет владеть плагинами. Этот класс отвечает за публикацию интерфейсов, предоставляемых плагинами.
2) класс плагина будет владеть компонентами, которые выполняют работу. Этот класс отвечает за регистрацию экспортированных интерфейсов и привязку импортированных интерфейсов интерфейсы к компонентам.
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() ); } };
вот минимальная реализация Заводского шаблона, которую я придумал примерно за 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. Мне нравится на что-то указывать:
- вам не нужно создавать конкретную фабрику для каждого класса. Шаблон сделает это за вас.
- мне нравится размещать весь заводской шаблон в своем собственном классе, чтобы вы не нужно беспокоиться о создании заводских объектов и их удалении. Вы просто регистрируете свои классы, заводской класс создается компилятором, а заводской объект создается шаблоном. В конце его жизни все фабрики полностью разрушаются. Мне нравится эта форма инкапсуляции, так как нет путаницы в том, кто управляет жизнью заводов.