C++ 如何设计一个简单的C++对象工厂?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/333400/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 14:43:06  来源:igfitidea点击:

How to design a simple C++ object factory?

c++factory

提问by kshahar

In my application, there are 10-20 classes that are instantiated once[*]. Here's an example:

在我的应用程序中,有 10-20 个类被实例化一次 [*]。下面是一个例子:

class SomeOtherManager;

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

Instances of the classes are contained in one object:

类的实例包含在一个对象中:

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

Currently TheManager uses the newoperator in order to create objects.

目前 TheManager 使用new操作符来创建对象。

My intention is to be able to replace, using plugins, the SomeManagerClass (or any other class) implementation with another one. In order to replace the implementation, 2 steps are needed:

我的目的是能够使用插件替换 SomeManagerClass (或任何其他类)实现与另一个。为了替换实现,需要2个步骤:

  1. Define a class DerivedSomeManagerClass, which inherits SomeManagerClass [plugin]
  2. Create the new class (DerivedSomeManagerClass) instead of the default (SomeManagerClass) [application]
  1. 定义一个类 DerivedSomeManagerClass,它继承 SomeManagerClass [plugin]
  2. 创建新类 (DerivedSomeManagerClass) 而不是默认的 (SomeManagerClass) [application]

I guess I need some kind of object factory, but it should be fairly simple since there's always only one type to create (the default implementation or the user implementation).

我想我需要某种对象工厂,但它应该相当简单,因为总是只有一种类型要创建(默认实现或用户实现)。

Any idea about how to design a simple factory like I just described? Consider the fact that there might be more classes in the future, so it should be easy to extend.

关于如何设计一个我刚刚描述的简单工厂的想法?考虑到未来可能会有更多的类,所以应该很容易扩展。

[*] I don't care if it happens more than once.

[*] 我不在乎它是否发生不止一次。

Edit:Please note that there are more than two objects that are contained in TheManager.

编辑:请注意,TheManager 中包含两个以上的对象。

采纳答案by UncleZeiv

I think there are two separate problems here.

我认为这里有两个不同的问题。

One problem is: how does TheManager namethe class that it has to create? It must keep some kind of pointer to "a way to create the class". Possible solutions are:

一个问题是:TheManager 如何命名它必须创建的类?它必须保留某种指向“创建类的方法”的指针。可能的解决方案是:

  • keeping a separate pointer for each kind of class, with a way to set it, but you already said that you don't like this as it violates the DRY principle
  • keeping some sort of table where the key is an enum or a string; in this case the setter is a single function with parameters (of course if the key is an enum you can use a vector instead of a map)
  • 为每种类保留一个单独的指针,并有设置它的方法,但是您已经说过您不喜欢这种方式,因为它违反了 DRY 原则
  • 保留某种表,其中键是枚举或字符串;在这种情况下,setter 是带有参数的单个函数(当然,如果键是枚举,则可以使用向量而不是映射)

The other problem is: what is this "way to create a class"? Unfortunately we can't store pointers to constructors directly, but we can:

另一个问题是:这种“创建类的方式”是什么?不幸的是,我们不能直接存储指向构造函数的指针,但我们可以:

  • create, as others have pointed out, a factory for each class
  • just add a static "create" function for each class; if they keep a consistent signature, you can just use their pointers to functions
  • 正如其他人指出的那样,为每个类创建一个工厂
  • 只需为每个类添加一个静态的“创建”函数;如果他们保持一致的签名,你可以使用他们的函数指针

Templates can help in avoiding unnecessary code duplication in both cases.

在这两种情况下,模板都有助于避免不必要的代码重复。

回答by Emiliano

Assuming a class (plugin1) which inherits from SomeManagerClass, you need a class hierarchy to build your types:

假设一个从 SomeManagerClass 继承的类 (plugin1),您需要一个类层次结构来构建您的类型:

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

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

Then you can assign those factories to a std::map, where they are bound to strings

然后你可以将这些工厂分配给 std::map,在那里它们绑定到字符串

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

Finally your TheManager just needs to know the name of the plugin (as string) and can return an object of type SomeManagerClass with just one line of code:

最后,您的 TheManager 只需要知道插件的名称(作为字符串),并且只需一行代码即可返回 SomeManagerClass 类型的对象:

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

EDIT: If you don't like to have one plugin factory class for each plugin, you could modify the previous pattern with this:

编辑:如果你不喜欢为每个插件有一个插件工厂类,你可以用这个修改以前的模式:

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

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

I think this is a much better solution. Moreover the 'plugin_factory' class could add itself to the 'factory_map' if you pass costructor the string.

我认为这是一个更好的解决方案。此外,如果向 costructor 传递字符串,“plugin_factory”类可以将自己添加到“factory_map”中。

回答by epatel

I have answered in another SO question about C++ factories. Please see thereif a flexible factory is of interest. I try to describe an old way from ET++ to use macros which has worked great for me.

我已经在另一个关于 C++ 工厂的 SO 问题中回答了。如果您对灵活的工厂感兴趣,请查看那里。我试图描述一种从 ET++ 到使用宏的旧方法,它对我很有用。

ET++was a project to port old MacApp to C++ and X11. In the effort of it Eric Gamma etc started to think about Design Patterns

ET++是一个将旧 MacApp 移植到 C++ 和 X11 的项目。在它的努力下,Eric Gamma 等开始考虑设计模式

回答by Joris Timmermans

I'd create a "base" factory that has virtual methods for creation of all the basic managers, and let the "meta manager" (TheManager in your question) take a pointer to the base factory as a constructor parameter.

我将创建一个“基础”工厂,它具有用于创建所有基本管理器的虚拟方法,并让“元管理器”(您的问题中的 TheManager)将指向基础工厂的指针作为构造函数参数。

I'm assuming that the "factory" can customize the instances of CXYZWManager by deriving from them, but alternatively the constructor of CXYZWManager could take different arguments in the "custom" factory.

我假设“工厂”可以通过从它们派生来自定义 CXYZWManager 的实例,但是 CXYZWManager 的构造函数可以在“自定义”工厂中采用不同的参数。

A lengthy code example that outputs "CSomeManager" and "CDerivedFromSomeManager":

输出“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;
  }

回答by kshahar

Here's the solution I thought of, it's not the best one but maybe it will help to think of better solutions:

这是我想到的解决方案,它不是最好的解决方案,但也许有助于想出更好的解决方案:

For each class there would be a creator class:

每个类都会有一个创建者类:

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

Then, the creators will be gathered in one class:

然后,创作者将聚集在一个班级:

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

And TheManager will be created with TheCreator for internal creation:

并且 TheManager 将使用 TheCreator 创建用于内部创建:

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

The problem with this solution is that it violates DRY - for each class creator I would have to write setter/getter in TheCreator.

此解决方案的问题在于它违反了 DRY - 对于每个类创建者,我都必须在 TheCreator 中编写 setter/getter。

回答by vishvananda

This seems like it would be a lot simpler with function templating as opposed to an Abstract Factory pattern

与抽象工厂模式相比,使用函数模板似乎要简单得多

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

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

If you want to get them via a string, you can create a standard map from strings to function pointers. Here is an implementation that works:

如果您想通过字符串获取它们,您可以创建一个从字符串到函数指针的标准映射。这是一个有效的实现:

#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: In reading the other answers I realized that this is very similar to Dave Van den Eynde's FactorySystem solution, but I'm using a function template pointer instead of instantiating templated factory classes. I think my solution is a little more lightweight. Due to static functions, the only object that gets instantiated is the map itself. If you need the factory to perform other functions (DestroyManager, etc.), I think his solution is more extensible.

编辑:在阅读其他答案时,我意识到这与 Dave Van den Eynde 的 FactorySystem 解决方案非常相似,但我使用的是函数模板指针,而不是实例化模板化工厂类。我认为我的解决方案更轻巧。由于静态函数,唯一被实例化的对象是地图本身。如果需要工厂来执行其他功能(DestroyManager等),我觉得他的解决方案更具扩展性。

回答by vishvananda

You should take a look at the tutorial at http://downloads.sourceforge.net/papafactory/PapaFactory20080622.pdf?use_mirror=fastbull

你应该看看http://downloads.sourceforge.net/papafactory/PapaFactory20080622.pdf?use_mirror=fastbull上的教程

It contains a great tutorial on implementing an Abstract factory in C++ and the source code that comes with it is also very robust

它包含一个关于在 C++ 中实现抽象工厂的很棒的教程,它附带的源代码也非常健壮

Chris

克里斯

回答by boutta

You could implement an object factory with static methods that return an instance of a Manager-Class. In the factory you could create a method for the default type of manager and a method for any type of manager which you give an argument representing the type of the Manager-Class (say with an enum). This last method should return an Interface rather than a Class.

您可以使用返回管理器类实例的静态方法来实现对象工厂。在工厂中,您可以为默认类型的管理器创建一个方法,并为任何类型的管理器创建一个方法,您可以提供一个表示管理器类类型的参数(例如使用枚举)。最后一个方法应该返回一个接口而不是一个类。

Edit: I'll try to give some code, but mind that my C++ times are quite a while back and I'm doing only Java and some scripting for the time being.

编辑:我会尝试提供一些代码,但请注意,我的 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;
     }
 };

Now you should be able to call the Factory via (if you've been able to make the code sample work):

现在您应该能够通过以下方式调用工厂(如果您已经能够使代码示例工作):

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

回答by David Allan Finch

I would use templates like this as I can't see the point of factories classes:

我会使用这样的模板,因为我看不到工厂类的意义:

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

回答by Vincent Robert

If you plan on supporting plugins that are dynamically linked, your program will need to provide a stable ABI (Application Binary Interface), that means that you cannot use C++ as your main interface as C++ has no standard ABI.

如果您计划支持动态链接的插件,您的程序将需要提供稳定的 ABI(应用程序二进制接口),这意味着您不能使用 C++ 作为主接口,因为 C++ 没有标准 ABI。

If you want plugins to implement an interface you define yourself, you will have to provide the header file of the interface to plugin programmer and standardize on a very simple C interface in order to create and delete the object.

如果你想让插件实现你自己定义的接口,你必须向插件程序员提供接口的头文件,并在一个非常简单的 C 接口上标准化,以便创建和删除对象。

You cannot provide a dynamic library that will allow you to "new" the plugin class as-is. That is why you need to standardize on a C interface in order to create the object. Using the C++ object is then possible as long as none of your arguments use possibly incompatible types, like STL containers. You will not be able to use a vector returned by another library, because you cannot ensure that their STL implementation is the same as yours.

您不能提供允许您按原样“新建”插件类的动态库。这就是为什么您需要在 C 接口上进行标准化以创建对象。只要您的参数不使用可能不兼容的类型(如 STL 容器),就可以使用 C++ 对象。您将无法使用其他库返回的向量,因为您无法确保它们的 STL 实现与您的相同。

Manager.h

经理.h

class Manager
{
public:
  virtual void doSomething() = 0;
  virtual int doSomethingElse() = 0;
}

extern "C" {
Manager* newManager();
void deleteManager(Manager*);
}

PluginManager.h

插件管理器.h

#include "Manager.h"

class PluginManager : public Manager
{
public:
  PluginManager();
  virtual ~PluginManager();

public:
  virtual void doSomething();
  virtual int doSomethingElse();
}

PluginManager.cpp

插件管理器.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()
{
  // ...
}