В моем приложении 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 шага:
Думаю, мне нужна какая-то фабрика объектов, но она должна быть довольно простой, поскольку всегда нужно создать только один тип (реализация по умолчанию или реализация пользователя).
Есть идеи, как спроектировать простую фабрику, как я только что описал? Учтите тот факт, что в будущем может быть больше классов, поэтому его будет легко расширять.
[*] Меня не волнует, случится ли это более одного раза.
Редактировать: Обратите внимание, что в TheManager содержится более двух объектов.
SomeManagerClass создается только один раз - при запуске приложения, поэтому я хочу изменить это создание. Однако это должно быть во время выполнения, поскольку код, заменяющий реализацию по умолчанию, находится в динамически связанном подключаемом модуле.
FWIW, я думаю, что всякий раз, когда вы называете класс «Менеджер», это происходит потому, что вы не знаете, что он на самом деле делает. Вы также можете называть это «ящиком» или «вещью». Имя - запах, имхо.





Вы можете реализовать объектную фабрику с помощью статических методов, которые возвращают экземпляр класса-менеджера. На фабрике вы можете создать метод для типа менеджера по умолчанию и метод для любого типа менеджера, который вы даете аргумент, представляющий тип класса менеджера (скажем, с перечислением). Этот последний метод должен возвращать интерфейс, а не класс.
Обновлено: я попытаюсь дать некоторый код, но помните, что мои времена 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;
}
};
Теперь у вас должна быть возможность вызвать Factory через (если вы смогли заставить образец кода работать):
Manager* manager = ManagerFactory.createManager(ManagerTypes.Manager1);
Спасибо за код. В своем вопросе я имел в виду, что хочу заменить «new Manager2 ();» операторы с созданием производных классов.
Я не понимаю, извините. Вы можете расширить перечисление и фабрику, чтобы наследовать другие классы.
Вот решение, которое я придумал, оно не лучшее, но, возможно, поможет придумать лучшие решения:
Для каждого класса будет свой класс-создатель:
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 - для каждого создателя класса мне пришлось бы написать сеттер / получатель в TheCreator.
Я не понимаю на сто процентов, и мне не очень нравятся фабричные штучки из книг и статей.
Если у всех ваших менеджеров одинаковый интерфейс, вы можете унаследовать его от базового класса и использовать этот базовый класс в своей программе. В зависимости от того, где будет принято решение о том, какой класс будет создан, вы должны использовать идентификатор для создания (как указано выше) или обрабатывать решение, какой менеджер создать внутри.
Другой способ - реализовать его «политику», например, с помощью шаблонов. Так что You ManagerClass :: create () возвращает конкретный экземпляр SomeOtherManagerWhatever. Это заложило бы решение о том, какой менеджер принять в коде, который использует ваш менеджер - Мэй это не предназначено.
Или так:
template<class MemoryManagment>
class MyAwesomeClass
{
MemoryManagment m_memoryManager;
};
(или что-то вроде того) С помощью этой конструкции вы можете легко использовать другие менеджеры, изменяя только экземпляр MyAwesomeClass.
Также класс для этой цели может быть немного чрезмерным. В вашем случае, я думаю, подойдет фабричная функция. Что ж, это больше вопрос личных предпочтений.
Я бы создал «базовую» фабрику, у которой есть виртуальные методы для создания всех основных менеджеров, и позволил бы «мета-менеджеру» (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;
}
Мне нравится это решение, но рассмотрим случай, когда TheManagerFactory необходимо вернуть 20 разных классов. Это означает, что он должен знать (используя оператор #include) все 20 классов.
Базовый фабричный класс может напрямую объявлять все типы менеджеров в заголовке (без зависимостей), и хотя cpp будет включать всех менеджеров, его нужно будет построить только один раз (или несколько раз). Настраиваемым фабрикам нужно только # включить тех менеджеров, которые они переопределяют.
Я бы использовал такие шаблоны, потому что не вижу смысла классов фабрик:
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>;
Если вы планируете поддерживать подключаемые модули, которые динамически связаны, ваша программа должна будет предоставить стабильный ABI (двоичный интерфейс приложения), это означает, что вы не можете использовать C++ в качестве основного интерфейса, поскольку C++ не имеет стандартного ABI.
Если вы хотите, чтобы плагины реализовывали интерфейс, который вы определяете самостоятельно, вам нужно будет предоставить файл заголовка интерфейса программисту плагинов и стандартизировать очень простой интерфейс C для создания и удаления объекта.
Вы не можете предоставить динамическую библиотеку, которая позволит вам «создать» класс плагина как есть. Вот почему вам необходимо стандартизировать интерфейс 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()
{
// ...
}
Я ответил на другой вопрос SO о фабриках C++. Если вас интересует гибкая фабрика, см. там. Я пытаюсь описать старый способ использования макросов из ET ++, который мне очень понравился.
ET ++ был проектом по переносу старого MacApp на C++ и X11. В результате Эрик Гамма и другие начали думать о Шаблоны проектирования.
Вы не говорили о TheManager. Похоже, вы хотите, чтобы это контролировало, какой класс используется? или, может быть, вы пытаетесь связать их вместе?
Похоже, вам нужен абстрактный базовый класс и указатель на текущий используемый класс. Если вы хотите создать цепочку, вы можете сделать это как в абстрактном классе, так и в классе диспетчера. Если абстрактный класс, добавьте члена к следующему классу в цепочке, если менеджер, то отсортируйте его, чтобы использовать в списке. Вам понадобится способ добавления классов, поэтому вам понадобится addMe () в диспетчере. Похоже, вы знаете, что делаете, поэтому, если вы выберете, должно быть правильно. Я рекомендую список с функцией addMe, и если вам нужен только 1 активный класс, тогда функция в TheManager решит, что это было бы хорошо.
Предполагая, что класс (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();
РЕДАКТИРОВАТЬ: если вам не нравится иметь один фабричный класс плагина для каждого плагина, вы можете изменить предыдущий шаблон следующим образом:
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 строку.
Я думаю, здесь есть две отдельные проблемы.
Одна проблема: как TheManager имя создает класс? Он должен содержать какой-то указатель на «способ создания класса». Возможные решения:
Другая проблема: что это за «способ создания класса»? К сожалению, мы не можем хранить указатели на конструкторы напрямую, но можем:
Шаблоны могут помочь избежать ненужного дублирования кода в обоих случаях.
Этот ответ заставил меня переосмыслить различные возможности дизайна и реализации. Это также полезно в качестве руководства к другим ответам, поэтому оно получает награду. Хочу поблагодарить всех за участие, у меня не хватает персонажей, чтобы всех вас благодарить :)
@UncleZeiv Спустя столько лет я все еще ненавижу тебя за «кражу» моего значка: P
@ Эмилиано, ты имеешь в виду награду :) и я даже проголосовал за тебя! <3
Это может быть тяжелее, чем вам нужно, но похоже, что вы пытаетесь создать рабочий класс фрейма, поддерживающий плагины.
Я бы разбил его на 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() );
}
};
Вот минимальная реализация фабричного шаблона, которую я придумал примерно за 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. Мне нравится что-то указывать:
Похоже, что с шаблонами функций было бы намного проще, чем с шаблоном абстрактной фабрики.
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 Дэйва Ван ден Эйнде, но я использую указатель шаблона функции вместо создания экземпляров шаблонных классов factory. Я думаю, что мое решение немного легче. Из-за статических функций единственный объект, который создается, - это сама карта. Если вам нужна фабрика для выполнения других функций (DestroyManager и т. д.), Я думаю, что его решение более расширяемое.
Вам следует взглянуть на руководство по адресу http://downloads.sourceforge.net/papafactory/PapaFactory20080622.pdf?use_mirror=fastbull
Он содержит отличное руководство по реализации абстрактной фабрики на C++, и исходный код, который поставляется с ним, также очень надежен.
Крис
Вы хотите заменить экземпляры SomeManagerClass во время выполнения или во время разработки?