我正在寻找一种直观和可扩展的方法来实现c ++中给定基类的子类的工厂.我想在库中提供这样的工厂函数.棘手的部分是我希望工厂也为用户定义的子类工作(例如,让库的工厂函数构建不同的子类,具体取决于链接到它的模块).目标是为下游开发人员使用工厂带来最小的负担/困惑.
我想要做的一个例子是:给定a std::istream,构造并返回与内容匹配的子类的对象,如果没有找到匹配则返回空指针.全球工厂的签名如下:
Base* Factory(std::istream &is){ ... };
Run Code Online (Sandbox Code Playgroud)
我熟悉原型工厂,但我更愿意避免制作/存储原型对象.这里发布了一个相关的问题:java:使用工厂允许最大的灵活性/可扩展性.
我目前不是在寻找c ++ 11特定的解决方案,但如果它们更优雅,我会很乐意了解这些.
我提出了一个工作解决方案,我相信它相当优雅,我将作为答案发布.我可以想象这个问题相当普遍,所以我想知道是否有人知道更好的方法.
编辑:似乎有些澄清是为了......
这个想法是让工厂构造派生类的对象,而不包含决定哪一个的逻辑.更糟糕的是,工厂方法最终将作为库的一部分,派生类可以在插件中定义.
派生类必须能够根据提供的输入(例如输入文件)自行决定它们是否适合构造.正如几个人所建议的那样,这个决定可以作为工厂可以使用的谓词来实现(顺便提一下,很好的建议!).
如果我理解正确,我们需要一个工厂函数,可以根据构造函数输入选择要实例化的派生类.这是迄今为止我能提出的最通用的解决方案.您可以指定映射输入以组织工厂函数,然后可以在工厂调用时指定构造函数输入.我不愿意说代码解释的比我说的要多,但我认为FactoryGen.hin 的示例实现Base.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();
}
}
};
Run Code Online (Sandbox Code Playgroud)
Base.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
Run Code Online (Sandbox Code Playgroud)
Derived.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
Run Code Online (Sandbox Code Playgroud)
Main.cpp的
#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
}
Run Code Online (Sandbox Code Playgroud)
我认为这是一个非常灵活/可扩展的工厂库.虽然它的代码不是非常直观,但我认为使用它非常简单.当然,我的看法是有偏见的,因为我是写作的人,所以如果相反,请告诉我.
编辑:清理FactoryGen.h文件.这可能是我的最后一次更新,但这是一个有趣的练习.
我的评论可能不太清楚.所以这里是一个依赖于模板元编程的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;
}
Run Code Online (Sandbox Code Playgroud)
好处:
缺点:
最后,使用原型设计模式可能会变得更好.我不知道,因为我没有尝试过使用我的代码.
我想陈述一些额外的事情(在进一步讨论聊天之后):
Produce(静态)成员函数.当且仅当它们不是nullptr时才存储对象.| 归档时间: |
|
| 查看次数: |
6309 次 |
| 最近记录: |