Как спроектировать простую фабрику объектов на C ++?

StackOverflow https://stackoverflow.com/questions/333400

  •  22-07-2019
  •  | 
  •  

Вопрос

В моем приложении есть 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 шага:

  1. Определите класс, производный от Somemanagerclass, который наследует SomeManagerClass [плагин]
  2. Создайте новый класс (DerivedSomeManagerClass) вместо класса по умолчанию (SomeManagerClass) [приложение]

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

Есть какие-нибудь идеи о том, как спроектировать простую фабрику, подобную той, которую я только что описал?Примите во внимание тот факт, что в будущем может появиться больше классов, поэтому его должно быть легко расширить.

[*] Меня не волнует, если это произойдет больше одного раза.

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

Это было полезно?

Решение

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

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

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

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

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

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

Другие советы

Предполагая, что класс (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', если вы передадите конструктору строку.

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

ET ++ был проектом по переносу старого MacApp на C ++ и X11. Эрик Гамма и другие начали задумываться об шаблонах проектирования

.

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

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

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

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

Кажется, что с шаблонизацией функций было бы намного проще, чем с шаблоном 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;
}

РЕДАКТИРОВАТЬ . Читая другие ответы, я понял, что это очень похоже на решение FactorySystem, разработанное Дейвом Ван ден Эйнде, но вместо создания шаблонных фабричных классов я использую указатель на шаблон функции. Я думаю, что мое решение немного более легкое. Из-за статических функций единственным объектом, который создается, является сама карта. Если вам нужна фабрика для выполнения других функций (DestroyManager и т. Д.), Я думаю, что его решение более расширяемое.

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

Редактировать: я попытаюсь дать немного кода, но учтите, что мои времена на 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);

Я бы использовал такие шаблоны, поскольку не вижу смысла в классах фабрик:

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 ++, и исходный код, который поставляется вместе с ним, также очень надежен

Крис

М-х, я не понимаю сто процентов, и я не очень люблю фабричные вещи из книг и статей.

<Ч>

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

<Ч>

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

Или так:

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

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

<Ч>

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

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

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

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

Менеджер.ч

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 затем сортируйте его, чтобы вы могли использовать в списке. Вам понадобится способ добавить классы, поэтому вам понадобится addMe () в менеджере. Похоже, вы знаете, что вы делаете, поэтому вы должны быть правы. Список с функцией addMe - моя рекомендация, и если вам нужен только 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.Мне нравится на что-то указывать:

    • Вам не нужно создавать конкретную фабрику для каждого класса.Шаблон сделает это за вас.
    • Мне нравится помещать весь шаблон factory в отдельный класс, чтобы вам не нужно было беспокоиться о создании объектов factory и их удалении.Вы просто регистрируете свои классы, фабричный класс создается компилятором, а фабричный объект создается шаблоном.В конце срока службы все заводы полностью разрушаются.Мне нравится такая форма инкапсуляции, поскольку нет путаницы в том, кто управляет сроком службы фабрик.
    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top