/ability_system

A head-only c++ framework of features: IOC, Component Patterns, Single Responsibility Principle, etc.

Primary LanguageC++MIT LicenseMIT

tag license compiler explorer

Ability System

面向对象编程虽然强大但也有一些缺点,本质上而言,面向对象编程将数据和方法强关联在了一起,程序员不得不面对一个非常臃肿的Class,Class中某些数据和方法是关联的,但并非所有的数据与所有的方法都一一关联,这就带来了冗余;而且在逻辑上,一个类往往拥有很多种能力,这导致可读性和复用性下降。

本框架设计的初衷就是为了将臃肿的大类拆分成若干个小类(结构体),用若干单一功能的小类组成一个复杂的大类,就像搭积木一样。这样可以增加代码的可读性,降低维护成本,拆分出的小类都是单一职责的,容易理解而且可以随意组合。由于采用了组件设计模式,一个类的能力取决于它有什么样的组件,这样做的好处是比继承更加灵活且高效(虚函数会增加开销),而且借助于C++ Template,组件是静态的,即在编译期实现的,不会产生额外的运行时开销,

Feature

  1. Header-only, Powerful, Tiny
  2. Ioc: Inversion of Control
  3. Static Component Pattern using C++ template
  4. Single Responsibility Principle
  5. Self-explanatory and Easy to read

Basic

本系统主要包含两个模板类:AbilityAbilityContainer和一个角色的概念

// 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,它的依赖是AC

Quick Start

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出现相同的角色,比如AFilterAData它们的角色都是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 

Compiler compatibility

  • Clang >= 5.0.0
  • GCC >= 7.1
  • MSVC >= 19.20