24

我正在寻找一种直观且可扩展的方法来为中给定基类的子类实现工厂。我想在库中提供这样的工厂函数。棘手的部分是我希望所说的工厂也适用于用户定义的子类(例如,让库的工厂函数根据链接到它的模块构建不同的子类)。目标是使下游开发人员使用工厂的负担/困惑最小化。

我想要做的一个例子是:给定一个std::istream,构造并返回一个与内容匹配的任何子类的对象,如果没有找到匹配项,则返回一个空指针。全球工厂的签名如下:

Base* Factory(std::istream &is){ ... };

我熟悉原型工厂,但我更喜欢避免制作/存储原型对象的需要。此处为发布了一个相关问题:Allowing maximal flexibly/extensibility using a factory

我目前不是在寻找特定于的解决方案,但如果它们更优雅,我会很乐意了解这些。

我想出了一个可行的解决方案,我认为它相当优雅,我将作为答案发布。我可以想象这个问题相当普遍,所以我想知道是否有人知道更好的方法。

编辑:似乎有一些澄清是为了......

这个想法是让工厂构造一个派生类的对象,而不包含决定哪一个的逻辑。更糟糕的是,工厂方法最终会成为库的一部分,而派生类可能会在插件中定义。

派生类必须能够根据提供的输入(例如输入文件)自行决定它们是否适合构造。正如几个人所建议的那样,这个决定可以作为工厂可以使用的谓词来实现(顺便说一句,这是个好建议!)。

4

7 回答 7

9

如果我理解正确,我们需要一个工厂函数,它可以根据构造函数输入选择要实例化的派生类。这是迄今为止我能想到的最通用的解决方案。您指定映射输入来组织工厂函数,然后您可以在工厂调用时指定构造函数输入。我讨厌说代码解释得比我能用语言解释的多,但是我认为在注释的帮助下,FactoryGen.hinBase.h和的示例实现Derived.h已经足够清晰了。如有必要,我可以提供更多详细信息。

FactoryGen.h

#pragma once

#include <map>
#include <tuple>
#include <typeinfo>

//C++11 typename aliasing, doesn't work in visual studio though...
/*
template<typename Base>
using FactoryGen<Base> = FactoryGen<Base,void>;
*/

//Assign unique ids to all classes within this map.  Better than typeid(class).hash_code() since there is no computation during run-time.
size_t __CLASS_UID = 0;

template<typename T>
inline size_t __GET_CLASS_UID(){
    static const size_t id = __CLASS_UID++;
    return id;
}

//These are the common code snippets from the factories and their specializations. 
template<typename Base>
struct FactoryGenCommon{
    typedef std::pair<void*,size_t> Factory; //A factory is a function pointer and its unique type identifier

    //Generates the function pointer type so that I don't have stupid looking typedefs everywhere
    template<typename... InArgs>
    struct FPInfo{ //stands for "Function Pointer Information"
        typedef Base* (*Type)(InArgs...);
    };

    //Check to see if a Factory is not null and matches it's signature (helps make sure a factory actually takes the specified inputs)
    template<typename... InArgs>
    static bool isValid(const Factory& factory){
        auto maker = factory.first;
        if(maker==nullptr) return false;

        //we have to check if the Factory will take those inArgs
        auto type = factory.second;
        auto intype = __GET_CLASS_UID<FPInfo<InArgs...>>();
        if(intype != type) return false;

        return true;
    }
};

//template inputs are the Base type for which the factory returns, and the Args... that will determine how the function pointers are indexed.
template<typename Base, typename... Args> 
struct FactoryGen : FactoryGenCommon<Base>{
    typedef std::tuple<Args...> Tuple;
    typedef std::map<Tuple,Factory> Map; //the Args... are keys to a map of function pointers

    inline static Map& get(){ 
        static Map factoryMap;
        return factoryMap; 
    }

    template<typename... InArgs>
    static void add(void* factory, const Args&... args){
        Tuple selTuple = std::make_tuple(args...); //selTuple means Selecting Tuple.  This Tuple is the key to the map that gives us a function pointer
        get()[selTuple] = Factory(factory,__GET_CLASS_UID<FPInfo<InArgs...>>());
    }

    template<typename... InArgs>
    static Base* make(const Args&... args, const InArgs&... inArgs){
        Factory factory = get()[std::make_tuple(args...)];
        if(!isValid<InArgs...>(factory)) return nullptr;
        return ((FPInfo<InArgs...>::Type)factory.first) (inArgs...);
    }
};

//Specialize for factories with no selection mapping
template<typename Base>
struct FactoryGen<Base,void> : FactoryGenCommon<Base>{
    inline static Factory& get(){
        static Factory factory;
        return factory; 
    }

    template<typename... InArgs>
    static void add(void* factory){
        get() = Factory(factory,__GET_CLASS_UID<FPInfo<InArgs...>>());
    }

    template<typename... InArgs>
    static Base* make(const InArgs&... inArgs){
        Factory factory = get();
        if(!isValid<InArgs...>(factory)) return nullptr;
        return ((FPInfo<InArgs...>::Type)factory.first) (inArgs...);
    }
};

//this calls the function "initialize()" function to register each class ONCE with the respective factory (even if a class tries to initialize multiple times)
//this step can probably be circumvented, but I'm not totally sure how
template <class T>
class RegisterInit {
  int& count(void) { static int x = 0; return x; } //counts the number of callers per derived
public:
  RegisterInit(void) { 
    if ((count())++ == 0) { //only initialize on the first caller of that class T
      T::initialize();
    }
  }
};

基数.h

#pragma once

#include <map>
#include <string>
#include <iostream>
#include "Procedure.h"
#include "FactoryGen.h"

class Base {
public:
    static Base* makeBase(){ return new Base; }
    static void initialize(){ FactoryGen<Base,void>::add(Base::makeBase); } //we want this to be the default mapping, specify that it takes void inputs

    virtual void speak(){ std::cout << "Base" << std::endl; }
};

RegisterInit<Base> __Base; //calls initialize for Base

派生的.h

#pragma once

#include "Base.h"

class Derived0 : public Base {
private:
    std::string speakStr;
public:
    Derived0(std::string sayThis){ speakStr=sayThis; }

    static Base* make(std::string sayThis){ return new Derived0(sayThis); }
    static void initialize(){ FactoryGen<Base,int>::add<std::string>(Derived0::make,0); } //we map to this subclass via int with 0, but specify that it takes a string input

    virtual void speak(){ std::cout << speakStr << std::endl; }
};

RegisterInit<Derived0> __d0init; //calls initialize() for Derived0

class Derived1 : public Base {
private:
    std::string speakStr;
public:
    Derived1(std::string sayThis){ speakStr=sayThis; }

    static Base* make(std::string sayThat){ return new Derived0(sayThat); }
    static void initialize(){ FactoryGen<Base,int>::add<std::string>(Derived0::make,1); } //we map to this subclass via int with 1, but specify that it takes a string input

    virtual void speak(){ std::cout << speakStr << std::endl; }
};

RegisterInit<Derived1> __d1init; //calls initialize() for Derived1

主文件

#include <windows.h> //for Sleep()
#include "Base.h"
#include "Derived.h"

using namespace std;

int main(){
    Base* b = FactoryGen<Base,void>::make(); //no mapping, no inputs
    Base* d0 = FactoryGen<Base,int>::make<string>(0,"Derived0"); //int mapping, string input
    Base* d1 = FactoryGen<Base,int>::make<string>(1,"I am Derived1"); //int mapping, string input

    b->speak();
    d0->speak();
    d1->speak();

    cout << "Size of Base: " << sizeof(Base) << endl;
    cout << "Size of Derived0: " << sizeof(Derived0) << endl;

    Sleep(3000); //Windows & Visual Studio, sry
}

我认为这是一个非常灵活/可扩展的工厂库。虽然它的代码不是很直观,但我认为使用它相当简单。当然,我的观点是有偏见的,因为我是写它的人,所以如果相反,请告诉我。

编辑:清理 FactoryGen.h 文件。这可能是我的最后一次更新,但这是一个有趣的练习。

于 2013-07-01T16:36:25.850 回答
6

我的评论可能不是很清楚。所以这是一个依赖于模板元编程的 C++11“解决方案”:(虽然可能不是最好的方法)

#include <iostream>
#include <utility>


// Type list stuff: (perhaps use an existing library here)
class EmptyType {};

template<class T1, class T2 = EmptyType>
struct TypeList
{
    typedef T1 Head;
    typedef T2 Tail;
};

template<class... Etc>
struct MakeTypeList;

template <class Head>
struct MakeTypeList<Head>
{
    typedef TypeList<Head> Type;
};

template <class Head, class... Etc>
struct MakeTypeList<Head, Etc...>
{
    typedef TypeList<Head, typename MakeTypeList<Etc...>::Type > Type;
};

// Calling produce
template<class TList, class BaseType>
struct Producer;

template<class BaseType>
struct Producer<EmptyType, BaseType>
{
    template<class... Args>
    static BaseType* Produce(Args... args)
    {
        return nullptr;
    }
};

template<class Head, class Tail, class BaseType>
struct Producer<TypeList<Head, Tail>, BaseType>
{
    template<class... Args>
    static BaseType* Produce(Args... args)
    {
        BaseType* b = Head::Produce(args...);
        if(b != nullptr)
            return b;
        return Producer<Tail, BaseType>::Produce(args...);
    }
};

// Generic AbstractFactory:
template<class BaseType, class Types>
struct AbstractFactory {
    typedef Producer<Types, BaseType> ProducerType;

    template<class... Args>
    static BaseType* Produce(Args... args)
    {
        return ProducerType::Produce(args...);
    }
};

class Base {}; // Example base class you had

struct Derived0 : public Base { // Example derived class you had
    Derived0() = default;
    static Base* Produce(int value)
    {
        if(value == 0)
            return new Derived0();
        return nullptr;
    }
};

struct Derived1 : public Base { // Another example class
    Derived1() = default;
    static Base* Produce(int value)
    {
        if(value == 1)
            return new Derived1();
        return nullptr;
    }
};

int main()
{
    // This will be our abstract factory type:
    typedef AbstractFactory<Base, MakeTypeList<Derived0, Derived1>::Type> Factory;
    Base* b1 = Factory::Produce(1);
    Base* b0 = Factory::Produce(0);
    Base* b2 = Factory::Produce(2);
    // As expected b2 is nullptr
    std::cout << b0 << ", " << b1 << ", " << b2 << std::endl;
}

优点:

  1. 没有(额外的)运行时开销,就像函数指针一样。适用于任何基本类型和任何数量的派生类型。当然,您仍然最终会调用这些函数。
  2. 多亏了可变参数模板,这适用于任意数量的参数(提供不正确数量的参数将产生编译时错误消息)。
  3. 不需要显式注册生产成员函数。

缺点:

  1. 当您声明工厂类型时,您的所有派生类型都必须可用。(您必须知道可能的派生类型是什么,并且它们必须是完整的。)
  2. 派生类型的产生成员函数必须是公共的。
  3. 可以使编译变慢。(在依赖模板元编程时总是如此)

最后,使用原型设计模式可能会更好。我不知道,因为我没有尝试使用我的代码。

我想说明一些额外的事情(在聊天进一步讨论之后):

  • 每个工厂只能返回一个对象。这看起来很奇怪,因为用户决定他们是否接受输入来创建他们的对象。出于这个原因,我建议您的工厂可以返回一组对象。
  • 小心不要使事情过于复杂。你想要一个插件系统,但我不认为你真的想要工厂。我建议您只需让用户注册他们的类(在他们的共享对象中),并且您只需将参数传递给类的Produce(静态)成员函数。当且仅当它们不是 nullptr 时,您才存储对象。
于 2013-07-01T12:43:36.687 回答
4

更新:这个答案假设存在某种可以读取并传递给工厂的魔法,但显然情况并非如此。我将答案留在这里是因为 a) 我可能会更新它,并且 b) 无论如何我都喜欢它。


与您自己的答案没有太大不同,不使用 C++11 技术(我还没有机会更新它,或者让它返回一个智能指针等),也不完全是我自己的工作,但这是我使用的工厂类。重要的是(恕我直言)它不会调用每个可能的类的方法来找到匹配的方法——它是通过映射来完成的。

#include <map>
// extraneous code has been removed, such as empty constructors, ...
template <typename _Key, typename _Base, typename _Pred = std::less<_Key> >
class Factory {
public:
    typedef _Base* (*CreatorFunction) (void);
    typedef std::map<_Key, CreatorFunction, _Pred> _mapFactory;

    // called statically by all classes that can be created
    static _Key Register(_Key idKey, CreatorFunction classCreator) {
        get_mapFactory()->insert(std::pair<_Key, CreatorFunction>(idKey, classCreator));
        return idKey;
    }

    // Tries to create instance based on the key
    static _Base* Create(_Key idKey) {
        _mapFactory::iterator it = get_mapFactory()->find(idKey);
        if (it != get_mapFactory()->end()) {
            if (it->second) {
                return it->second();
            }
        }
        return 0;
    }

protected:
    static _mapFactory * get_mapFactory() {
        static _mapFactory m_sMapFactory;
        return &m_sMapFactory;
    }
};

要使用它,您只需声明基本类型,并为每个类将其注册为静态。请注意,当您注册时,会返回密钥,因此我倾向于将其添加为类的成员,但这不是必需的,只是整洁:) ...

// shape.h
// extraneous code has been removed, such as empty constructors, ...
// we also don't technically need the id() method, but it could be handy
// if at a later point you wish to query the type.
class Shape {
public:
    virtual std::string id() const = 0;
};
typedef Factory<std::string, Shape> TShapeFactory;

现在我们可以创建一个新的派生类,并将其注册为可创建的TShapeFactory...

// cube.h
// extraneous code has been removed, such as empty constructors, ...
class Cube : public Shape {
protected:
    static const std::string _id;
public:
    static Shape* Create() {return new Cube;}
    virtual std::string id() const {return _id;};
};

// cube.cpp
const std::string Cube::_id = TShapeFactory::Register("cube", Cube::Create);

然后我们可以创建一个新项目,在这种情况下,是一个字符串:

Shape* a_cube = TShapeFactory::Create("cube");
Shape* a_triangle = TShapeFactory::Create("triangle");
// a_triangle is a null pointer, as we've not registered a "triangle"

这种方法的优点是,如果您创建一个新的派生的、工厂可生成的类,则无需更改任何其他代码,只要您可以看到工厂类并从基类派生:

// sphere.h
// extraneous code has been removed, such as empty constructors, ...
class Sphere : public Shape {
protected:
    static const std::string _id;
public:
    static Shape* Create() {return new Sphere;}
    virtual std::string id() const {return _id;};
};

// sphere.cpp
const std::string Sphere::_id = TShapeFactory::Register("sphere", Sphere::Create);

我将留给读者的可能改进包括添加诸如:typedef _Base base_classto之类的内容Factory,以便在您声明自定义工厂时,您可以使您的类派生自TShapeFactory::base_class等等。工厂可能还应该检查一个密钥是否已经存在,但是再次......它作为一个练习留下。

于 2013-07-01T15:35:08.640 回答
3

我目前能想到的最佳解决方案是使用一个Factory类,该类存储指向为每个派生类生成函数的指针。当创建一个新的派生类时,可以将指向生产方法的函数指针存储在工厂中。

这是一些代码来说明我的方法:

#include <iostream>
#include <vector>

class Base{};

// Factory class to produce Base* objects from an int (for simplicity).
// The class uses a list of registered function pointers, which attempt
// to produce a derived class based on the given int.
class Factory{
public:
    typedef Base*(*ReadFunPtr)(int);
private:
    static vector<ReadFunPtr> registeredFuns;
public:
    static void registerPtr(ReadFunPtr ptr){ registeredFuns.push_back(ptr); }
    static Base* Produce(int value){
        Base *ptr=NULL;
        for(vector<ReadFunPtr>::const_iterator I=registeredFuns.begin(),E=registeredFuns.end();I!=E;++I){
            ptr=(*I)(value);
            if(ptr!=NULL){
                return ptr;
            }
        }
        return NULL;
    }
};
// initialize vector of funptrs
std::vector<Factory::ReadFunPtr> Factory::registeredFuns=std::vector<Factory::ReadFunPtr>();

// An example Derived class, which can be produced from an int=0. 
// The producing method is static to avoid the need for prototype objects.
class Derived : public Base{
    private:
        static Base* ProduceDerivedFromInt(int value){ 
            if(value==0) return new Derived();
            return NULL;
        }
public:
    Derived(){};

    // registrar is a friend because we made the producing function private
    // this is not necessary, may be desirable (e.g. encapsulation)
    friend class DerivedRegistrar;
};

// Register Derived in the Factory so it will attempt to construct objects.
// This is done by adding the function pointer Derived::ProduceDerivedFromInt
// in the Factory's list of registered functions.
struct DerivedRegistrar{ 
    DerivedRegistrar(){ 
        Factory::registerPtr(&(Derived::ProduceDerivedFromInt));
    }
} derivedregistrar;

int main(){
    // attempt to produce a Derived object from 1: should fail
    Base* test=Factory::Produce(1);
    std::cout << test << std::endl; // outputs 0

    // attempt to produce a Derived object from 0: works
    test=Factory::Produce(0);
    std::cout << test << std::endl; // outputs an address
}

TL;DR:在这种方法中,下游开发人员需要将派生类的生产函数实现为static成员函数(或非成员函数),并使用简单的struct.

这看起来很简单,不需要任何原型对象。

于 2013-06-29T09:57:02.207 回答
3

这是管理在运行时解析的工厂的可持续惯用语。我过去曾使用它来支持相当复杂的行为。我喜欢简单性和可维护性,而不会放弃太多功能。

TLDR:

  • 一般避免静态初始化
  • 避免像瘟疫这样的“自动加载”技术
  • 沟通对象和工厂的所有权
  • 单独的使用和工厂管理问题

使用运行时工厂

这是该工厂系统的用户将与之交互的基本界面。他们不应该担心工厂的细节。

class BaseObject {
public:
    virtual ~BaseObject() {}
};

BaseObject* CreateObjectFromStream(std::istream& is);

顺便说一句,我建议使用引用boost::optional, 或shared_ptr代替原始指针。在一个完美的世界里,界面应该告诉我谁拥有这个对象。作为用户,我是否有责任在收到此指针时将其删除?当它是shared_ptr.

实现运行时工厂

在另一个标题中,放置管理工厂活动范围的详细信息。

class RuntimeFactory {
public:
    virtual BaseObject* create(std::istream& is) = 0;
};

void RegisterRuntimeFactory(RuntimeFactory* factory);
void UnregisterRuntimeFactory(RuntimeFactory* factory);

我认为所有这一切的重点是使用与工厂的初始化和使用方式不同。

我们应该注意到这些自由函数的调用者拥有工厂。注册表不拥有它们。

这并不是绝对必要的,尽管它可以更好地控制这些工厂何时何地被摧毁。重要的是当您看到诸如“创建后”或“销毁前”调用之类的内容时。具有这些名称的工厂方法是所有权倒置的设计气味。

无论如何,围绕它编写另一个包装器来管理工厂的生命周期已经足够简单了。它也有助于组合,这是更好的。

注册您的新工厂

为每个工厂注册编写包装器。我通常将每个工厂注册放在自己的标题中。这些标头通常只是两个函数调用。

void RegisterFooFactory();
void UnregisterFooFactory();

这可能看起来有点矫枉过正,但这种勤奋可以缩短编译时间。

我的mainthen 简化为一堆注册和注销调用。

#include <foo_register.h>
#include <bar_register.h>

int main(int argc, char* argv[]) {
    SetupLogging();
    SetupRuntimeFactory();
    RegisterFooFactory();
    RegisterBarFactory();

    // do work...

    UnregisterFooFactory();
    UnregisterBarFactory();
    CleanupLogging();
    return 0;
}

避免静态初始化陷阱

这特别避免了像其他一些解决方案一样在静态加载期间创建的对象。这不是意外。

  • C++ 规范不会为您提供有关何时发生静态加载的有用保证
  • 当出现问题时,您将获得堆栈跟踪
  • 代码简单,直接,易于遵循

实施注册表

正如您想象的那样,实现细节相当普通。

class RuntimeFactoryRegistry {
public:
    void registerFactory(RuntimeFactory* factory) {
        factories.insert(factory);
    }

    void unregisterFactory(RuntimeFactory* factory) {
        factories.erase(factory);
    }

    BaseObject* create(std::istream& is) {
        std::set<RuntimeFactory*>::iterator cur = factories.begin();
        std::set<RuntimeFactory*>::iterator end = factories.end();
        for (; cur != end; cur++) {
            // reset input?
            if (BaseObject* obj = (*cur)->create(is)) {
                return obj;
            }
        }
        return 0;
    }

private:
    std::set<RuntimeFactory*> factories;
};

这假设所有工厂都是互斥的。放宽这个假设不太可能导致性能良好的软件。我可能会亲自提出更强有力的主张,呵呵。另一种选择是返回对象列表。

为了简化演示,以下实现是静态的。这对于多线程环境可能是个问题。它不必是静态的,我也不建议它应该或不应该是静态的,它就在这里。这不是真正讨论的主题,所以我将保留它。

这些自由函数仅充当此实现的传递函数。如果您愿意,这使您可以对注册表进行单元测试或重用它。

namespace {

    static RuntimeFactoryRegistry* registry = 0;

} // anon    

void SetupRuntimeFactory() {
    registry = new RuntimeFactoryRegistry;
}

void CleanupRuntimeFactory() {
    delete registry;
    registry = 0;
}

BaseObject* CreateObjectFromStream(std::istream& is) {
    return registry->create(is);
}

void RegisterRuntimeFactory(RuntimeFactory* factory) {
    registry->registerFactory(factory);
}

void UnregisterRuntimeFactory(RuntimeFactory* factory) {
    registry->unregisterFactory(factory);
}
于 2013-07-03T02:05:43.683 回答
1

First, there's not really enough detail here to form an opinion, so I'm left to guess. You've provided a challenging question and a minimal solution, but not clarified what is wrong with your solution.

I suspect the complaint centers around the reset back to knowing nothing between a refused construction and the following construction attempts. Given a very large number of potential factories this reset could have us parsing the same data hundreds or thousands of times. If this is the problem the question is this: how do you structure the predicate evaluation phase to limit the amount of work, and allow it to reuse previous parsing results.

I suggest having each factory register with: 1) a factory builder function taking the specialization parameter(s) (iostream in the example) 2) an unordered set of boolean predicates 3) required boolean values of each predicate to allow construction

The set of predicates is used to create/modify the predicate tree. Interior nodes in the tree represent predicates (branching to 'pass', 'fail', and possibly 'don't care'). Both interior nodes and leaves hold constructors which are satisfied if the ancestral predicates are satisfied. As you traverse the tree you first look for constructors at the current level, then evaluate the predicate and follow the required path. If no solution is found along that child path the follow the 'don't care' path.

This allows new factories to share predicate functions. There's probably lots of questions about managing/sorting the tree when the factories go on/off line. There's also the possibility of parser state data that needs to be retained across predicates and reset when construction is completed. There's lots of open questions, but this may work toward addressing the perceived problems with your solution.

TL:DR; Create a graph of predicates to traverse when attempting construction.

于 2013-07-03T06:37:54.777 回答
0

简单的解决方案只是一个开关盒:

Base *create(int type, std::string data) {
  switch(type) { 
   case 0: return new Derived1(data); 
   case 1: return new Derived2(data);
  };
}

但它只是决定你想要哪种类型:

   int type_of_obj(string s) {
      int type = -1;
      if (isderived1(s)) type=0;
      if (isderived2(s)) type=1;
      return type;
   } 

然后它只是连接两者:

Base *create_obj(string s, string data, 
                 Base *(*fptr)(int type, string data), 
                 int (*fptr2)(string s)) 
{
   int type = fptr2(s);
   if (type==-1) return 0;
   return fptr(type, data);   
}

然后它只是注册函数指针:

   class Registry {
   public:
       void push_back(Base* (*fptr)(int type, string data),
                      int (*fptr2)(string s));
       Base *create(string s, string data);
   };

该插件将具有 2 个功能,以及以下功能:

void register_classes(Registry &reg) {
    reg.push_back(&create, &type_of_obj);
    ...
}

插件加载器将对 register_classes 函数进行 dlopen/dlsym。

(另一方面,我自己并没有使用这种插件,因为创建新插件的工作量太大。我有更好的方法来为我的程序片段提供模块化。扼杀插件的是你需要修改你的构建系统来创建新的 dll 或 shared_libs,而这样做的工作量太大了——理想情况下,新模块只是一个类;没有任何更复杂的构建系统修改)

于 2013-07-02T16:58:50.620 回答