面向对象编程虽然强大但也有一些缺点,本质上而言,面向对象编程将数据和方法强关联在了一起,程序员不得不面对一个非常臃肿的Class,Class中某些数据和方法是关联的,但并非所有的数据与所有的方法都一一关联,这就带来了冗余;而且在逻辑上,一个类往往拥有很多种能力,这导致可读性和复用性下降。
本框架设计的初衷就是为了将臃肿的大类
拆分成若干个小类
(结构体),用若干单一功能的小类
组成一个复杂的大类
,就像搭积木一样。这样可以增加代码的可读性,降低维护成本,拆分出的小类都是单一职责的,容易理解而且可以随意组合。由于采用了组件设计模式,一个类的能力取决于它有什么样的组件,这样做的好处是比继承更加灵活且高效(虚函数会增加开销),而且借助于C++ Template,组件是静态的,即在编译期实现的,不会产生额外的运行时开销,
- Header-only, Powerful, Tiny
- Ioc: Inversion of Control
- Static Component Pattern using C++ template
- Single Responsibility Principle
- Self-explanatory and Easy to read
本系统主要包含两个模板类:Ability
、AbilityContainer
和一个角色
的概念
// Ability System Role
namespace asr {
struct A;
struct B;
struct C;
}
class A: public Ability<asr::A> {};
class B: public Ability<asr::B, asr::A, asr::C> {};
class C: public Ability<asr::C, asr::A> {};
class ABC: public AbilityContainer<A, B, C> {};
AbilityContainer
类是变长模板
的大类
,由若干小类
组成,可以接任意个数的小类。角色
是小类
的模板参数,可以理解成对于某个功能的命名和抽象,大部分情况下角色就是一个类型声明。Ability
类是变长模板
的小类
,各个小类
之间有依赖关系,依赖关系由其后面的模板参数决定,第一个表示自己的角色,剩余的表示其依赖的角色,所有至少要一个模板(必须标定自己的角色,可以没有依赖)。struct B: public Ability<asr::C, asr::A, asr::C>
的含义是:对于B类,它的角色是B
,它的依赖是A
和C
。
run it online : compiler explorer
假设我们现在要设计一个数据库中的员工信息表 我们需要完成三个功能:
- 描述表头
- 描述表格内容
- 记录表格的选中状态
#include <vector>
#include <string>
#include <map>
struct User {
std::string id;
std::string name;
std::string sex;
int age;
};
class Table {
public:
std::vector<std::string> titles() { ... }
std::vector<std::string> keys() { ... }
std::vector<std::string> display(std::string id) { ... }
bool select(std::string id) { ... }
private:
...
};
正常情况下,我们可以设计出类似上述代码的接口。
#include <vector>
#include <string>
#include <map>
#include <iostream>
#include <iomanip>
struct User {
std::string id;
std::string name;
std::string sex;
int age;
};
namespace asr {
struct Title;
struct Display;
struct Select;
struct Data;
struct Keys;
}
struct ATitle;
struct ADisplay;
struct ASelect;
struct AData;
struct AKeys;
struct ATitle: public Ability<asr::Title> {
std::vector<std::string> asInvoke() {
return std::vector<std::string> {"Id", "Name", "Sex", "Age"};
}
};
struct AData: public Ability<asr::Data> {
std::map<std::string, User> userMap;
AData() {
userMap["01"] = User{"01", "Tom", "m", 18};
userMap["02"] = User{"02", "Lucy", "f", 17};
}
};
struct ASelect: public Ability<asr::Select, asr::Data> {
std::map<std::string, bool> selected;
AData* pData;
void asInit(AData& Data) { pData = &Data; }
bool asInvoke(std::string id) {
if (!selected.count(id)) {
selected[id] = true;
return true;
}
selected.erase(id);
return false;
}
};
struct ADisplay: public Ability<asr::Display, asr::Data> {
AData* pData;
void asInit(AData& Data) { pData = &Data; }
std::vector<std::string> asInvoke(std::string id) {
User& user = pData->userMap[id];
std::vector<std::string> list;
list.push_back(user.id);
list.push_back(user.name);
list.push_back(user.sex);
list.push_back(std::to_string(user.age));
return list;
}
};
struct AKeys: public Ability<asr::Keys, asr::Data> {
AData* pData;
void asInit(AData& Data) { pData = &Data; }
std::vector<std::string> asInvoke() {
std::vector<std::string> keys;
for (auto item: pData->userMap) {
keys.push_back(item.first);
}
return keys;
}
};
class Table: public AbilityContainer<ASelect, ADisplay, AKeys, AData, ATitle> {
public:
std::vector<std::string> titles() { return invoke<asr::Title>(); }
std::vector<std::string> keys() { return invoke<asr::Keys>(); }
std::vector<std::string> display(std::string id) { return invoke<asr::Display>(id); }
bool select(std::string id) { return invoke<asr::Select>(id); }
};
int main() {
Table table;
for (auto data: table.invoke<asr::Title>()) {
std::cout << std::setw(8) << data << " ";
}
std::cout << std::endl;
for (auto key: table.keys()) {
for (auto data: table.invoke<asr::Display>(key)) {
std::cout << std::setw(8) << data << " ";
}
std::cout << std::endl;
}
}
输出:
Id Name Sex Age
01 Tom m 18
02 Lucy f 17
我们的代码还可以进一步简化
class Table: public AbilityContainer<ASelect, ADisplay, AKeys, AData, ATitle> {};
int main() {
Table table;
for (auto data: table.invoke<asr::Title>()) {
std::cout << std::setw(8) << data << " ";
}
std::cout << std::endl;
for (auto key: table.keys()) {
for (auto data: table.invoke<asr::Display>(key)) {
std::cout << std::setw(8) << data << " ";
}
std::cout << std::endl;
}
}
为什么要引入一个中间变量如asr::Display
,asr命名空间下面的类都只有声明,仅起到标识的作用,但是引入它们是非常有必要的,主要有两个原因,一是为了避免相互引用,A->B && B->A,相互引用相互依赖是可能发生的;第二是为了给系统加入层的概念,举例说明:
struct AFilter: public Ability<asr::Data, asr::Data>, public asr::Data {
std::map<std::string, User> userMapExtra;
void asInit(AData& pData) {
userMapExtra = pData.userMap;
userMapExtra["03"] = User{"03", "Hanmei", "f", 16};
}
virtual User& GetUser(std::string id) override {
return userMapExtra[id];
}
virtual std::vector<std::string> Keys() override {
std::vector<std::string> keys;
for (auto item: userMapExtra) {
keys.push_back(item.first);
}
return keys;
}
};
class Table: public AbilityContainer<ASelect, ADisplay, AKeys, AFilter, AData, ATitle> {};
对于每个Ability
的子类,它们的Ioc顺序是和它们在AbilityContainer中的位置有关的,它会从自己所在位置的后一个开始搜索,一直往后循环一圈(即顺序搜索到最后一个,再跳转到第一个顺序搜索到自己的前一个)。
对于一个AbilityContainer
出现相同的角色,比如AFilter
和AData
它们的角色都是asr::Data
,那么那一个生效取决于它们的位置,按照搜索顺序,对于ASelect
ADisplay
AKeys
ATitle
所有查找到的asr::Data
,实际上都是AFilter
,如果更换顺序为 class Table: public AbilityContainer<ASelect, ADisplay, AFilter, AKeys, AData, ATitle> {};
那么对于AKeys
她找到的就是AData
。
这里说的层非常类似photeshop中的图层,上面的图层会遮盖下面的图层。
- 对于
大类
而言,若出现同名的角色
,只会暴露出最上面的那一个,这个例子中,大类
只暴露了AFilter
,而AData
被隐藏了 - 层的性质非常适合用虚函数和继承的方式来实现
加入Filter后的完整代码如下
run it online : compiler explorer
#include <iostream>
#include <string>
#include <map>
#include <vector>
#include <iomanip>
struct User {
std::string id;
std::string name;
std::string sex;
int age;
};
namespace asr {
struct Title;
struct Display;
struct Select;
struct Data {
virtual User& GetUser(std::string) = 0;
virtual std::vector<std::string> Keys() = 0;
};
struct Keys;
}
struct ATitle;
struct ADisplay;
struct ASelect;
struct AData;
struct AKeys;
struct AFilter;
struct ATitle: public Ability<asr::Title> {
std::vector<std::string> asInvoke() {
return std::vector<std::string> {"Id", "Name", "Sex", "Age"};
}
};
struct AData: public Ability<asr::Data>, public asr::Data {
std::map<std::string, User> userMap;
AData() {
userMap["01"] = User{"01", "Tom", "m", 18};
userMap["02"] = User{"02", "Lucy", "f", 17};
}
virtual User& GetUser(std::string id) override {
return userMap[id];
}
virtual std::vector<std::string> Keys() override {
std::vector<std::string> keys;
for (auto item: userMap) {
keys.push_back(item.first);
}
return keys;
}
};
struct ASelect: public Ability<asr::Select, asr::Data> {
std::map<std::string, bool> selected;
asr::Data* pData;
void asInit(asr::Data& Data) { pData = &Data; }
bool asInvoke(std::string id) {
if (!selected.count(id)) {
selected[id] = true;
return true;
}
selected.erase(id);
return false;
}
};
struct ADisplay: public Ability<asr::Display, asr::Data> {
asr::Data* pData;
void asInit(asr::Data& Data) { pData = &Data; }
std::vector<std::string> asInvoke(std::string id) {
User& user = pData->GetUser(id);
std::vector<std::string> list;
list.push_back(user.id);
list.push_back(user.name);
list.push_back(user.sex);
list.push_back(std::to_string(user.age));
return list;
}
};
struct AKeys: public Ability<asr::Keys, asr::Data> {
asr::Data* pData;
void asInit(asr::Data& Data) { pData = &Data; }
std::vector<std::string> asInvoke() {
std::vector<std::string> keys = pData->Keys();
return keys;
}
};
struct AFilter: public Ability<asr::Data, asr::Data>, public asr::Data {
std::map<std::string, User> userMapExtra;
void asInit(AData& pData) {
userMapExtra = pData.userMap;
userMapExtra["03"] = User{"03", "Hanmei", "f", 16};
}
virtual User& GetUser(std::string id) override {
return userMapExtra[id];
}
virtual std::vector<std::string> Keys() override {
std::vector<std::string> keys;
for (auto item: userMapExtra) {
keys.push_back(item.first);
}
return keys;
}
};
class Table: public AbilityContainer<ASelect, ADisplay, AKeys, AFilter, AData, ATitle> {
public:
std::vector<std::string> titles() { return invoke<asr::Title>(); }
std::vector<std::string> keys() { return invoke<asr::Keys>(); }
std::vector<std::string> display(std::string id) { return invoke<asr::Display>(id); }
bool select(std::string id) { return invoke<asr::Select>(id); }
};
int main() {
Table table;
for (auto data: table.invoke<asr::Title>()) {
std::cout << std::setw(8) << data << " ";
}
std::cout << std::endl;
for (auto key: table.keys()) {
for (auto data: table.invoke<asr::Display>(key)) {
std::cout << std::setw(8) << data << " ";
}
std::cout << std::endl;
}
}
输出:
Id Name Sex Age
01 Tom m 18
02 Lucy f 17
03 Hanmei f 16
- Clang >= 5.0.0
- GCC >= 7.1
- MSVC >= 19.20