需求
假如有以下场景:你的程序需要读文件,文件第一行是数据类型,后面的是数据,例如这样
Date
2021-10-11
2022-10-11
2023-10-11
Name
张三
李四
wangwu
有一个基类TypeHandlerBase
,还有一批继承了它的子类:DataHandler
、NameHandler
、... ,他们被用来处理不同类型的数据,比如NameHandler
逐行解析名字,把名打印出来。DataHandler
逐行解析日期,把日期插入不同的数据库。这些类都实现了process(file)
函数,该函数负责完成上诉处理数据的流程
如果使用常规做法,就是读取第一行,然后一堆if else
判断来new
一个对应的handler
,伪代码如下:
int main() {
string type;
open(filename);
type = getline(filename); // 读首行
TypeHandlerBase* handler;
if (type == "Name") {
handler = new NameHandler();
} else if (type == "Date") {
handler = new DateHandler();
} else if ...
handler->process(filename)
}
如果handler
子类数量过多,你的if else
会变的非常长且丑陋,本文写一个根据字符串自动加载对应类的方法——动态工厂
你的程序需要做的事是:读程序的第一行,根据读出来的字符串调用对应的handler
类,然后执行handler->process(file)
来处理文件,使用该方法,你的调用将会变成这样,伪代码如下:
int main (filename) {
string type
open(filename)
// 读首行,解析type
type = getline(filename)
// 获取对应的handler类
TypeHandlerBase* handler = DynamicFactory<TypeHandlerBase>::instance()->create(type + “Handler”)
handler->process(filename)
}
本篇文章教你如何写一个动态工厂实现上面的需求
知识补充
首先要掌握一些知识,如果懂可以跳过
typeid关键字
typeid 返回一个变量或数据类型的“类型”。
#include <typeinfo>
namespace test{
class Myclass{ };
}
int main () {
cout << typeid(int).name() << endl;
cout << typeid(double).name() << endl;
cout << typeid(test::Myclass).name() << endl;
}
输出结果:
i
d
N4test7MyclassE
可以看到,test::Myclass
通过typeid()
关键字输出变成了N4test7MyclassE
,这是因为编译器修饰了符号,那么怎么获取正确的名字呢?要用到cxxabi
这个库
abi::__cxa_demangle获取可读类名
abi::__cxa_demangle()
这个函数传入typeid
就可以获得真的类名了
#include <cxxabi.h>
static std::string ReadTypeName(const char* name) {
char* real_name = abi::__cxa_demangle(name, nullptr, nullptr, nullptr);
std::string real_name_string(real_name);
free(real_name);
return real_name_string;
}
编写工厂类
通过上面的知识补充,我们现在可以通过一个类型T
获取对应的T
的名字了,我们来逐渐完善工厂类吧,工厂类DynamicFactory<TBase>
用于创建TBase
类派生的类型。
- 首先,一个工厂是一个单例,需要
instence()
函数, - 其次,工厂类里需要有一个
map
成员变量, key是T
名字字符串作为,value是返回基类TBase
的函数指针, 我们姑且命名为create_function_map_
. - 然后,我们需要一个
注册函数
来往map里面注册我们定义的class们,就要用到上一节定义的ReadTypeName
函数解析出真实的类名注册进去 - 最后我们再定义一个
create()
函数用来在map
里查找类并返回函数指针并调用,创建一个对应类型的实例
template <typename TBase>
class DynamicFactory {
public:
typedef TBase* (*CreateFunction)();
// instance
static DynamicFactory* instance() {
static DynamicFactory fac;
return &fac;
}
// 注册函数
bool regist(const char* name, CreateFunction func) {
if (!func) {
return false;
}
std::string type_name = common::ReadTypeName(name);
return create_function_map_.insert(std::make_pair(type_name, func)).second;
}
//
TBase* create(const std::string& type_name) {
typename std::map<std::string, CreateFunction>::iterator it =
create_function_map_.find(type_name);
if (it == create_function_map_.end()) {
return NULL;
}
return it->second();
}
}
private:
std::map<std::string, CreateFunction> create_function_map_;
}
好了,工厂类创建好了,我们需要用某个类的时候,可以这么获得
TypeHandlerBase* p_name_handler = DynamicFactory<TypeHandlerBase>::instance()
->create("test::NameHandler");
p_name_handler->process(file_name);
调用是好调用,但是注册怎么注册呢?下一章教大家写一种自动注册的方法
编写对象创建器
称DynamicCreate
为动态创建器类,当我们要在动态工厂中注册一个子类时,只需要继承动态创建器类即可,例如NameHandler
:
class NameHandler : public DynamicCreate<NameHandler, TypeHandlerBase>
动态创建器类的模板参数是<类自己的名字, 基类名>
怎么做到的呢?看DynamicCreate<T, TBase>
,关键就是s_registor
这个静态成员变量,利用静态变量
在程序运行的时候就会初始化这个性质,在struct registor
构造函数中完成了向工厂类里注册的操作。
最后别忘了在class外面对static成员变量进行声明
// 动态对象创建器
template <typename T, typename TBase>
class DynamicCreate : public TBase {
public:
static TBase* CreateObject() { return new T(); }
struct registor {
// 构造函数里完成注册
registor() {
if (!DynamicFactory<TBase>::instance()->regist(typeid(T).name(), CreateObject)) {
assert(false);
}
}
};
// 静态成员变量,程序运行时即初始化
static registor s_registor;
public:
DynamicCreate() {}
virtual ~DynamicCreate() {}
};
// 在class外面声明static成员变量
template <typename T, typename TBase>
typename DynamicCreate<T, TBase>::registor DynamicCreate<T, TBase>::s_registor;
好啦,大功告成,再看一下静态工厂的用法:
int main (filename) {
string type
open(filename)
// 读首行,解析type
type = getline(filename)
// 通过动态工厂获取对应的handler类
TypeHandlerBase* handler = DynamicFactory<TypeHandlerBase>::instance()->create(type + “Handler”);
handler->process(filename)
}
当文件第一行是Name
,handler会得到一个NameHandler
.