C++利用反射和簡單工廠模式實現業務模塊解耦

1. 業務說明

爲了便於說明,舉一個簡單的例子。假設現在有一個項目需要建立一個和銀行交互的平臺,目前只接入工商銀行,後續接入其他銀行,每個銀行的業務都有差異,報文格式可能也不一致。

這裏只列舉幾個簡要的流程,僅包括拼報文,發送報文,接收報文,解析報文,其餘整體架構以及後續處理等內容省略。

2. 初步設計

創建一個銀行交互類 BankOpt,包括四個函數:

int setMsg();       //拼報文
int sendMsg();      //發送報文
int getMsg();       //接收報文
int parseMsg();     //解析報文

然後在每個函數中通過if-else來判斷具體是哪一個銀行,之後進行相應的處理。

這種設計在剛開發的時候非常方便,代碼量少,但是如果後續需要接入另外一個銀行時就需要改動BankOpt類,不符合設計模式中的開放-封閉原則。而且單個函數中將來可能會有大量的if-else,使代碼可讀性下降。

3. 簡單工廠模式

通過簡單工廠模式,我們可以創建一個專門的工廠類用於實例化一個合適的銀行交互類,只需要這個銀行交互類具有共同的接口即可。

首先,爲了實現更好的複用,把各個銀行交互類中相同的部分抽象出來,形成一個銀行交互基類,代碼如下:

class BaseBank
{
public:
    virtual int setMsg() = 0;
    virtual int sendMsg() = 0;
    virtual int getMsg() = 0;
    virtual int parseMsg() = 0;
};

這裏僅僅聲明瞭四個純虛函數,具體的業務邏輯在子類中實現。

創建兩個銀行交互子類GSBank(工商銀行)和RMBank(人民銀行),繼承BaseBank,實現四個虛函數。

  • 創建一個工廠類
class BankFactory
{
public:
    BaseBank* createBank(const string& bank_name) {
    if (bank_name == “GSBank”) 
        return new GSBank();
    else if (bank_name == “RMBank”)
        return new RMBank();
    }
};

工廠類中有一個createBank函數,用於根據銀行編碼創建相應的實例並返回其基類指針,這樣我們只需要通過基類指針調用相關函數即可。

  • 在主流程中調用
BankFactory bf;
BaseBank* t = (BaseBank*)bf.createBank(bank_name);
if (t == NULL) {
    cout << "銀行編碼錯誤!" << endl;
    return 2;
}
t->setMsg();
t->sendMsg();
t->getMsg();
t->parseMsg();
  • 優缺點
    利用簡單工廠模式,當我們後續接入另外的銀行時,只需要添加具體的銀行交互類,實現業務函數,然後在工廠類的createBank函數中添加一個else if子句。相對於原來的設計已經改進很多了,但是仍然需要修改原來的工廠類的代碼,沒有徹底實現解耦。

4. 反射

反射在java的一些框架中使用的比較多,而且用起來非常方便。C++本身並不支持,但是我們可以模擬一些簡單的特性。

我們需要一種能夠根據字符串動態獲取對應的銀行交互類的實例的方法。這樣在工廠類的createBank方法中就可以根據字符串直接獲取對應銀行交互類的實例,而不需要再每次通過新增else if 子句來新增一個銀行接口。

也就是說,利用反射和簡單工廠模式,下次當我們需要新增一個銀行接口的時候只需要新增一個銀行交互類即可,不需要修改原來的任何代碼,實現了業務上的解耦。

  • 如何在C++中實現反射
    1. 需要一個全局的map用於存儲類的信息以及創建實例的函數
    2. 需要反射的類需要提供一個用於創建自身實例的函數
    3. 利用類的靜態變量在程序啓動的時候會進行初始化來在全局map中將類名及創建實例的函數存入map中

相關代碼如下:

typedef void* (*register_func)();

class Class
{
public:
static void* newInstance(const string& class_name) {
map<string, register_func>::iterator it = m_register.find(class_name);
    if (it == m_register.end())
        return NULL;
    else
        return it->second();
}
static void registerClass(const string& class_name, register_func func) {
    m_register[class_name] = func;
}

private:
    /* key is class name and value is function to create instance of class */
    static map<string, register_func> m_register;
};


class Register
{
public:
    Register(const string& class_name, register_func func) {
        Class::registerClass(class_name, func);
    }
};

#define REGISTER_CLASS(class_name) \
    class class_name##Register { \
    public: \
        static void* newInstance() { \
            return new class_name; \
        } \
    private: \
        static const Register reg; \
    };\
const Register class_name##Register::reg(#class_name,class_name##Register::newInstance);

還需要修改工廠類的createBank函數,利用Class的newInstance函數來創建實例:

BaseBank* createBank(const string& bank_name) {
        return (BaseBank*)Class::newInstance(bank_name);
}

Class類中的m_register變量是static類型的map,相當於全局變量。

newInstance函數,傳入類名,查找map,調用回調函數,返回一個對應類的實例。
registerClass函數傳入類名和用於創建實例的回調函數並將信息存入全局的map中。

Register類只有一個構造函數,會調用Class的registerClass函數完成註冊。

利用宏定義,在每一個需要反射的類後面額外增加一個類,其中有一個Register類型的static const變量,這樣在程序啓動的時候就會完成初始化調用Register類的構造函數,完成註冊。

之後只需要在需要反射的類,例如在工商銀行交互類 GSBank 後面加上一條宏定義:
REGISTER_CLASS(GSBank) 就可以通過工廠類傳入”GSBank”字符串獲得工商銀行交互類的實例。

5. 測試

工商銀行

人民銀行

通過傳入不同的銀行編碼,會實例化不同的銀行交互類,並且執行其對應的函數。

如果需要增加新的銀行接口,例如農業銀行,只需要新增一個NYBank類,實現具體的業務邏輯,不需要改動原來的任何代碼,傳入NYBank字符串,就會執行農業銀行相關的處理流程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章