C++11 實現(xiàn)一個自動注冊的工廠
之前在項目代碼里面看到同事寫了個自動注冊的工廠類,雖然當時我看不懂,但我大受震撼。
今天又重新溫習(xí)了一下這種寫法,分享給大家,可見學(xué)好 C++ 是多么的重要。
實現(xiàn)動機
工廠方法是最簡單地創(chuàng)建派生類對象的方法,也是很常用的,工廠方法內(nèi)部使用switch-case根據(jù)不同的key去創(chuàng)建不同的派生類對象,下面是一個偽代碼。
Message*?create(int?type)
{
????switch?(type)?
????{
????case?MSG_PGSTATS:
????????m?=?new?MPGStats;
????????break;
????case?MSG_PGSTATSACK:
????????m?=?new?MPGStatsAck;
????????break;
????case?CEPH_MSG_STATFS:
????????m?=?new?MStatfs;
????????break;
????case?CEPH_MSG_STATFS_REPLY:
????????m?=?new?MStatfsReply;
????????break;
????case?MSG_GETPOOLSTATS:
????????m?=?new?MGetPoolStats;
????????break;
????default:
????????break;
????}
}
隨著時間的流逝,消息種類越來越多,這個switch-case會越來越長,我在一個開源項目中看到過一百多個case語句,顯然這種簡單工廠已經(jīng)不堪負荷,這樣的代碼對于維護者來說也是一個噩夢。
要消除這些長長的switch-case語句是一個需要解決的問題,而自動注冊的對象工廠則是一個比較優(yōu)雅的解決方案。
自動注冊的對象工廠遵循了開放-封閉原則,新增對象時無需修改原有代碼,僅僅需要擴展即可,徹底地消除了switch-case語句。
實現(xiàn)方法
自動注冊的對象工廠的實現(xiàn)思路如下:
提供一個單例工廠對象。 工廠注冊對象(保存創(chuàng)建對象的key和構(gòu)造器)。 利用輔助類,在輔助類對象的構(gòu)造過程中實現(xiàn)目標對象地注冊。 利用一個宏來生成輔助對象。 在派生類文件中調(diào)用這個宏實現(xiàn)自動注冊。
其中,需要注意的是,對象工廠并不直接保存對象,而是對象的構(gòu)造器,因為對象工廠不是對象池,是對象的生產(chǎn)者,允許不斷地創(chuàng)建實例,另外,這樣做還實現(xiàn)了延遲創(chuàng)建。
另外一個要注意的地方是借助宏來實現(xiàn)自動注冊,本質(zhì)上是通過宏來定義了很多全局的靜態(tài)變量,而這些靜態(tài)變量僅僅是為了實現(xiàn)自動注冊,并沒有實際的意義。
下面來看看如何用 C++11 來實現(xiàn)這個自動注冊的對象工廠。
一個單例的對象工廠代碼
struct?factory
{
????static?factory&?get()
????{
????????static?factory?instance;
????????return?instance;
????}
private:
????factory()?{};
????factory(const?factory&)?=?delete;
????factory(factory&&)?=?delete;
????static?std::map<std::string,?std::function>?map_;?
};
在C++11中單例的實現(xiàn)非常簡單,返回一個一個靜態(tài)局部變量的引用即可,而且這個方法還是線程安全的,因為C++11中靜態(tài)局部變量的初始化是線程安全的。
工廠內(nèi)部有一個map,map的值類型為一個function,是對象的構(gòu)造器。
對象工廠的輔助類的代碼
struct?factory
{
????template<typename?T>
????struct?register_t
????{
????????register_t(const?std::string&?key)
????????{
????????????factory::get().map_.emplace(key,?[]{?return?new?T;?});
????????}
????};
private:
????inline?static?factory&?get()
????{
????????static?factory?instance;
????????return?instance;
????}
????
????static?std::map<std::string,?FunPtr>?map_;
};
對象工廠的輔助類register_t是工廠類的一個內(nèi)部模版類,非常簡單,只有一個構(gòu)造函數(shù),這個構(gòu)造函數(shù)中調(diào)用了factory的私有變量map_,并往map_中插入了key和泛型對象的構(gòu)造器。
這里用到了C++11的一個新特性:內(nèi)部類可以通過外部類的實例訪問外部類的私有成員,所以register_t可以直接訪問factory的私有變量map_。
自動注冊的代碼
#define?REGISTER_MESSAGE_VNAME(T)?reg_msg_##T##_
#define?REGISTER_MESSAGE(T,?key,?...)?static?factory::register_t?REGISTER_MESSAGE_VNAME(T)(key,?__VA_ARGS__);
在派生類中調(diào)用宏注冊自己:
class?Message1?:?public?Message
{
????//……
};
REGISTER_MESSAGE(Message1,?"message1");
自動注冊的關(guān)鍵是通過一個宏來生成靜態(tài)全局的register_t的實例,因為register_t的實例是用來向工廠注冊目標對象的構(gòu)造器。
所以僅僅需要在派生類中調(diào)用這個宏就可以實現(xiàn)自動至注冊了,而無需修改原有代碼。
我們還可以添加智能指針接口,無需讓用戶管理原始指針,甚至讓工廠能創(chuàng)建帶任意參數(shù)的對象。
Factory最終的實現(xiàn)
#include?
#include?
#include?
#include?
#include?"Message.hpp"
struct?factory
{
????template<typename?T>
????struct?register_t
????{
????????register_t(const?std::string&?key)
????????{
????????????factory::get().map_.emplace(key,?[]?{?return?new?T();?});
????????}
????????template<typename...?Args>
????????register_t(const?std::string&?key,?Args...?args)
????????{
????????????factory::get().map_.emplace(key,?[&]?{?return?new?T(args...);?});
????????}
????};
????static?Message*?produce(const?std::string&?key)
????{
????????if?(map_.find(key)?==?map_.end())
????????????throw?std::invalid_argument("the?message?key?is?not?exist!");
????????return?map_[key]();
????}
????static?std::unique_ptr?produce_unique(const?std::string&?key)
???? {
????????return?std::unique_ptr(produce(key));
????}
????static?std::shared_ptr?produce_shared(const?std::string&?key)
???? {
????????return?std::shared_ptr(produce(key));
????}
private:
????factory()?{};
????factory(const?factory&)?=?delete;
????factory(factory&&)?=?delete;
????static?factory&?get()
????{
????????static?factory?instance;
????????return?instance;
????}
????static?std::map<std::string,?std::function>?map_;
};
std::map<std::string,?std::function>?factory::map_;
#define?REGISTER_MESSAGE_VNAME(T)?reg_msg_##T##_
#define?REGISTER_MESSAGE(T,?key,?...)?static?factory::register_t?REGISTER_MESSAGE_VNAME(T)(key,?##__VA_ARGS__);
示例:
class?Message
{
public:
????virtual?~Message()?{}
????virtual?void?foo()
????{
????}
};
#include?"MessageFactory.hpp"
#include?"Message.hpp"
class?Message1?:?public?Message
{
public:
????Message1()
????{
????????std::cout?<"message1"?<std::endl;
????}
????Message1(int?a)
????{
????????std::cout?<"message1"?<std::endl;
????}
????~Message1()
????{
????}
????void?foo()?override
????{
????????std::cout?<"message1"?<std::endl;
????}
};
//REGISTER_MESSAGE(Message1,?"message1",?2);
REGISTER_MESSAGE(Message1,?"message1");
#include?"Message1.hpp"
int?main()
{
????Message*?p?=?factory::produce("message1");
????p->foo();???//Message1
????
????auto?p2?=?factory::produce_unique("message1");
????p2->foo();
}
總結(jié):
使用C++11,僅僅需要幾十行代碼就可以實現(xiàn)一個自動注冊的對象工廠,消除了長長的swithc-case語句,還遵循了開閉原則,簡潔而優(yōu)雅。
完整代碼可以參考:
https://github.com/qicosmos/cosmos/tree/master/self-register-factory
作者:qicosmos
地址:https://www.cnblogs.com/qicosmos/p/5090159.html

技術(shù)交流,歡迎加我微信:ezglumes ,拉你入技術(shù)交流群。
私信領(lǐng)取相關(guān)資料
推薦閱讀:
開通專輯 | 細數(shù)那些年寫過的技術(shù)文章專輯
覺得不錯,點個在看唄~

