之前在項目代碼裡面看到同事寫了個自動註冊的工廠類,雖然當時我看不懂,但我大受震撼。
今天又重新溫習了一下這種寫法,分享給大家,可見學好 C++ 是多麼的重要。
實現動機工廠方法是最簡單地創建派生類對象的方法,也是很常用的,工廠方法內部使用switch-case根據不同的key去創建不同的派生類對象,下面是一個偽代碼。
Message*create(inttype){switch(type){caseMSG_PGSTATS:m=newMPGStats;break;caseMSG_PGSTATSACK:m=newMPGStatsAck;break;caseCEPH_MSG_STATFS:m=newMStatfs;break;caseCEPH_MSG_STATFS_REPLY:m=newMStatfsReply;break;caseMSG_GETPOOLSTATS:m=newMGetPoolStats;break;default:break;}}隨着時間的流逝,消息種類越來越多,這個switch-case會越來越長,我在一個開源項目中看到過一百多個case語句,顯然這種簡單工廠已經不堪負荷,這樣的代碼對於維護者來說也是一個噩夢。
要消除這些長長的switch-case語句是一個需要解決的問題,而自動註冊的對象工廠則是一個比較優雅的解決方案。
自動註冊的對象工廠遵循了開放-封閉原則,新增對象時無需修改原有代碼,僅僅需要擴展即可,徹底地消除了switch-case語句。
實現方法自動註冊的對象工廠的實現思路如下:
其中,需要注意的是,對象工廠並不直接保存對象,而是對象的構造器,因為對象工廠不是對象池,是對象的生產者,允許不斷地創建實例,另外,這樣做還實現了延遲創建。
另外一個要注意的地方是藉助宏來實現自動註冊,本質上是通過宏來定義了很多全局的靜態變量,而這些靜態變量僅僅是為了實現自動註冊,並沒有實際的意義。
下面來看看如何用 C++11 來實現這個自動註冊的對象工廠。
一個單例的對象工廠代碼structfactory{staticfactory&get(){staticfactoryinstance;returninstance;}private:factory(){};factory(constfactory&)=delete;factory(factory&&)=delete;staticstd::map<std::string,std::function<Message*()>>map_;};在C++11中單例的實現非常簡單,返回一個一個靜態局部變量的引用即可,而且這個方法還是線程安全的,因為C++11中靜態局部變量的初始化是線程安全的。
工廠內部有一個map,map的值類型為一個function,是對象的構造器。
對象工廠的輔助類的代碼structfactory{template<typenameT>structregister_t{register_t(conststd::string&key){factory::get().map_.emplace(key,[]{returnnewT;});}};private:inlinestaticfactory&get(){staticfactoryinstance;returninstance;}staticstd::map<std::string,FunPtr>map_;};對象工廠的輔助類register_t是工廠類的一個內部模版類,非常簡單,只有一個構造函數,這個構造函數中調用了factory的私有變量map_,並往map_中插入了key和泛型對象的構造器。
這裡用到了C++11的一個新特性:內部類可以通過外部類的實例訪問外部類的私有成員,所以register_t可以直接訪問factory的私有變量map_。
自動註冊的代碼#defineREGISTER_MESSAGE_VNAME(T)reg_msg_##T##_#defineREGISTER_MESSAGE(T,key,...)staticfactory::register_t<T>REGISTER_MESSAGE_VNAME(T)(key,__VA_ARGS__);在派生類中調用宏註冊自己:
classMessage1:publicMessage{//……};REGISTER_MESSAGE(Message1,"message1");自動註冊的關鍵是通過一個宏來生成靜態全局的register_t的實例,因為register_t的實例是用來向工廠註冊目標對象的構造器。
所以僅僅需要在派生類中調用這個宏就可以實現自動至註冊了,而無需修改原有代碼。
我們還可以添加智能指針接口,無需讓用戶管理原始指針,甚至讓工廠能創建帶任意參數的對象。
Factory最終的實現#include<map>#include<string>#include<functional>#include<memory>#include"Message.hpp"structfactory{template<typenameT>structregister_t{register_t(conststd::string&key){factory::get().map_.emplace(key,[]{returnnewT();});}template<typename...Args>register_t(conststd::string&key,Args...args){factory::get().map_.emplace(key,[&]{returnnewT(args...);});}};staticMessage*produce(conststd::string&key){if(map_.find(key)==map_.end())throwstd::invalid_argument("themessagekeyisnotexist!");returnmap_[key]();}staticstd::unique_ptr<Message>produce_unique(conststd::string&key){returnstd::unique_ptr<Message>(produce(key));}staticstd::shared_ptr<Message>produce_shared(conststd::string&key){returnstd::shared_ptr<Message>(produce(key));}private:factory(){};factory(constfactory&)=delete;factory(factory&&)=delete;staticfactory&get(){staticfactoryinstance;returninstance;}staticstd::map<std::string,std::function<Message*()>>map_;};std::map<std::string,std::function<Message*()>>factory::map_;#defineREGISTER_MESSAGE_VNAME(T)reg_msg_##T##_#defineREGISTER_MESSAGE(T,key,...)staticfactory::register_t<T>REGISTER_MESSAGE_VNAME(T)(key,##__VA_ARGS__);示例:classMessage{public:virtual~Message(){}virtualvoidfoo(){}};#include"MessageFactory.hpp"#include"Message.hpp"classMessage1:publicMessage{public:Message1(){std::cout<<"message1"<<std::endl;}Message1(inta){std::cout<<"message1"<<std::endl;}~Message1(){}voidfoo()override{std::cout<<"message1"<<std::endl;}};//REGISTER_MESSAGE(Message1,"message1",2);REGISTER_MESSAGE(Message1,"message1");#include"Message1.hpp"intmain(){Message*p=factory::produce("message1");p->foo();//Message1autop2=factory::produce_unique("message1");p2->foo();}總結:使用C++11,僅僅需要幾十行代碼就可以實現一個自動註冊的對象工廠,消除了長長的swithc-case語句,還遵循了開閉原則,簡潔而優雅。
完整代碼可以參考:
https://github.com/qicosmos/cosmos/tree/master/self-register-factory
作者:qicosmos
地址:https://www.cnblogs.com/qicosmos/p/5090159.html
技術交流,歡迎加我微信:ezglumes ,拉你入技術交流群。
私信領取相關資料
推薦閱讀:
音視頻開發工作經驗分享 || 視頻版
OpenGL ES 學習資源分享
開通專輯 | 細數那些年寫過的技術文章專輯
NDK 學習進階免費視頻來了
你想要的音視頻開發資料庫來了
推薦幾個堪稱教科書級別的 Android 音視頻入門項目
覺得不錯,點個在看唄~
