C++利用反射和簡單工廠模式實現業務模組解耦
1. 業務說明
為了便於說明,舉一個簡單的例子。假設現在有一個專案需要建立一個和銀行互動的平臺,目前只接入工商銀行,後續接入其他銀行,每個銀行的業務都有差異,報文格式可能也不一致。
這裡只列舉幾個簡要的流程,僅包括拼報文,傳送報文,接收報文,解析報文,其餘整體架構以及後續處理等內容省略。
2. 初步設計
建立一個銀行互動類 BankOpt,包括四個函式:
int setMsg(); //拼報文
int sendMsg(); //傳送報文
int getMsg(); //接收報文
int parseMsg(); //解析報文
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
然後在每個函式中通過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;
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
這裡僅僅聲明瞭四個純虛擬函式,具體的業務邏輯在子類中實現。
建立兩個銀行互動子類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();
}
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
工廠類中有一個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();
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 優缺點
利用簡單工廠模式,當我們後續接入另外的銀行時,只需要新增具體的銀行互動類,實現業務函式,然後在工廠類的createBank函式中新增一個else if子句。相對於原來的設計已經改進很多了,但是仍然需要修改原來的工廠類的程式碼,沒有徹底實現解耦。
4. 反射
反射在java的一些框架中使用的比較多,而且用起來非常方便。C++本身並不支援,但是我們可以模擬一些簡單的特性。
我們需要一種能夠根據字串動態獲取對應的銀行互動類的例項的方法。這樣在工廠類的createBank方法中就可以根據字串直接獲取對應銀行互動類的例項,而不需要再每次通過新增else if 子句來新增一個銀行介面。
也就是說,利用反射和簡單工廠模式,下次當我們需要新增一個銀行介面的時候只需要新增一個銀行互動類即可,不需要修改原來的任何程式碼,實現了業務上的解耦。
- 如何在C++中實現反射
- 需要一個全域性的map用於儲存類的資訊以及建立例項的函式
- 需要反射的類需要提供一個用於建立自身例項的函式
- 利用類的靜態變數在程式啟動的時候會進行初始化來在全域性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);
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
還需要修改工廠類的createBank函式,利用Class的newInstance函式來建立例項:
BaseBank* createBank(const string& bank_name) {
return (BaseBank*)Class::newInstance(bank_name);
}
- 1
- 2
- 3
- 1
- 2
- 3
Class類中的m_register變數是static型別的map,相當於全域性變數。
newInstance函式,傳入類名,查詢map,呼叫回撥函式,返回一個對應類的例項。
registerClass函式傳入類名和用於建立例項的回撥函式並將資訊存入全域性的map中。
Register類只有一個建構函式,會呼叫Class的registerClass函式完成註冊。
利用巨集定義,在每一個需要反射的類後面額外增加一個類,其中有一個Register型別的static const變數,這樣在程式啟動的時候就會完成初始化呼叫Register類的建構函式,完成註冊。
之後只需要在需要反射的類,例如在工商銀行互動類 GSBank 後面加上一條巨集定義:
REGISTER_CLASS(GSBank) 就可以通過工廠類傳入”GSBank”字串獲得工商銀行互動類的例項。
5. 測試
通過傳入不同的銀行編碼,會例項化不同的銀行互動類,並且執行其對應的函式。
如果需要增加新的銀行介面,例如農業銀行,只需要新增一個NYBank類,實現具體的業務邏輯,不需要改動原來的任何程式碼,傳入NYBank字串,就會執行農業銀行相關的處理流程。