C++反射机制的实现源码
简介
《大话设计模式》中说:“所有在用简单工厂的地方,都可以考虑用反射技术来去除switch或if,解除分支判断带来的耦合。”Java、C#语言本身就支持反射,使用起来很方便,但C++语言本身却没有反射。再加上最近看了太多的switch、if代码,实在难受,就想能否在C++里也用上反射技术重构代码,既能解耦,又易于扩展,顺便体现一下编程的艺术感。
就我目前所知,反射就是根据字符串生成相应的类或调用相应的函数,从而将原本在编译期就确定的实例化过程或函数调用转移到在运行时确定,这样带来的好处就是仅改动一点代码或者不需要改动任何代码就能快速响应需求,符合设计模式中的开闭原则。
问题描述
基于《大话设计模式》第15章的多数据库切换问题,在C++中引入反射机制,改进抽象工厂模式。
思路及源码
- 源码链接:https://github.com/Ylttx/Design-mode/tree/master/factory_reflection
- 用哈希表存储映射:<类名字符串,生成对应类的函数指针>,用一个字符串获取
db
类型:
class DataAccess {
public:
static IUser* CreateUser();
static IDepartment* CreateDepartment();
private:
DataAccess() {}
const static std::string db;
const static std::unordered_map<std::string, pCreateObject> m_class;
};
- 定义一个注册哈希映射的宏定义,便于插入值以及创建生成类的函数:
#define REGISTER_CLASS(className) \
className* Create##className() { \
return new className; \
}
#define REGISTER(className) {#className, (pCreateObject) Create##className}
- 在注册映射表之前,先定义数据库的两张表:
User、Department
,定义类及相应的访问接口。
class User;
class IUser {
public:
virtual ~IUser() {}
virtual void Insert(const User &user) = 0;
virtual User* GetUser(int id) = 0;
};
class User {
public:
int getId() {return _id;}
void setId(int id) {_id = id;}
const std::string& getName() {return _name;}
void setName(std::string name) {_name = name;}
private:
int _id;
std::string _name;
};
class Department;
class IDepartment {
public:
virtual ~IDepartment() {}
virtual void Insert(const Department &Department) = 0;
virtual Department* GetDepartment(int id) = 0;
};
class Department {
public:
int getId() {return _id;}
void setId(int id) {_id = id;}
const std::string& getDeptName() {return _deptName;}
void setDeptName(std::string name) {_deptName = name;}
private:
int _id;
std::string _deptName;
};
- 定义两种数据库的访问接口,并使用宏定义创建类的生成函数:
class AccessUser : public IUser {
public:
void Insert(const User &user) override {
std::cout << "在Access中给User表增加一条记录" << std::endl;
}
User* GetUser(int id) override {
std::cout << "在Access中根据ID得到User表一条记录" << std::endl;
User *user = new User;
return user;
}
};
REGISTER_CLASS(AccessUser)
class AccessDepartment : public IDepartment {
public:
void Insert(const Department &Department) {
std::cout << "在Access中给Department表增加一条记录" << std::endl;
}
Department* GetDepartment(int id) {
std::cout << "在Access中根据ID得到Department表一条记录" << std::endl;
Department *dept = new Department;
return dept;
}
};
REGISTER_CLASS(AccessDepartment);
class SqlserverUser : public IUser {
public:
void Insert(const User &user) override {
std::cout << "在SQL Server中给User表增加一条记录" << std::endl;
}
User* GetUser(int id) override {
std::cout << "在SQL Server中根据ID得到User表一条记录" << std::endl;
User *user = new User;
return user;
}
};
REGISTER_CLASS(SqlserverUser)
class SqlserverDepartment : public IDepartment {
public:
void Insert(const Department &Department) {
std::cout << "在SQL Server中给Department表增加一条记录" << std::endl;
}
Department* GetDepartment(int id) {
std::cout << "在SQL Server中根据ID得到Department表一条记录" << std::endl;
Department *dept = new Department;
return dept;
}
};
REGISTER_CLASS(SqlserverDepartment)
- 定义
DataAccess
类生成接口,用REGISTER
宏定义来注册哈希映射:
const std::string DataAccess::db = "Sqlserver";
const std::unordered_map<std::string, pCreateObject> DataAccess::m_class = {
REGISTER(AccessUser),
REGISTER(AccessDepartment),
REGISTER(SqlserverUser),
REGISTER(SqlserverDepartment),
};
IUser* DataAccess::CreateUser() {
auto it = m_class.find(db + "User");
if (it == m_class.end()) {
return 0;
}
return (IUser*) it->second();
}
IDepartment* DataAccess::CreateDepartment() {
auto it = m_class.find(db + "Department");
if (it == m_class.end()) {
return 0;
}
return (IDepartment*) it->second();
}
- 测试函数
#include "DataAccess.h"
#include <iostream>
int main() {
User user;
Department dept;
IUser *iu = DataAccess::CreateUser();
iu->Insert(user);
iu->GetUser(2);
delete iu;
IDepartment *id = DataAccess::CreateDepartment();
id->Insert(dept);
id->GetDepartment(1);
delete id;
return 0;
}
- Test it!
$ g++ -std=c++14 -Wall -o main main.cpp
$ ./main
$ 在SQL Server中给User表增加一条记录
在SQL Server中根据ID得到User表一条记录
在SQL Server中给Department表增加一条记录
在SQL Server中根据ID得到Department表一条记录
# 切换数据库
# const std::string DataAccess::db = "Access";
$ ./main
$ Access中给User表增加一条记录
在Access中根据ID得到User表一条记录
在Access中给Department表增加一条记录
在Access中根据ID得到Department表一条记录
总结与改进
用反射加上简单工厂模式改进抽象工厂模式,确实感受到了一点编程的艺术感。相比Java的反射来说,C++除了要用哈希表来存储映射之外,还要在每个具体类后再定义一个类生成函数,还是略显繁琐。
我的实现用了一个硬编码的字符串来存储数据库名,其实是不太妥当的,应该考虑使用配置文件的方式来存储这些字段,这样需要更换数据库时只要在其中配置就可以了,而无需修改程序重新编译。
<?xml version="1.0" encoding="UTF-8">
<configuration>
<settings>
<add key="DB" value="Sqlserver"/>
</settings>
</configuration>