一个c++动态工厂类,通过解析字符串加载不同类对象

817 阅读4分钟

需求

假如有以下场景:你的程序需要读文件,文件第一行是数据类型,后面的是数据,例如这样

Date
2021-10-11
2022-10-11
2023-10-11
Name
张三
李四
wangwu

有一个基类TypeHandlerBase,还有一批继承了它的子类:DataHandlerNameHandler、... ,他们被用来处理不同类型的数据,比如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.