工廠方法是最簡單地創建派生類對象的方法,也是很常用的,工廠方法內部使用switch-case根據不同的key去創建不同的派生類對象,下面是一個僞代碼。
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語句,顯然這種簡單工廠已經不堪負荷,這樣的代碼對於維護者來說也是一個噩夢。要消除這些長長的switch-case語句是一個需要解決的問題,而自動註冊的對象工廠則是一個比較優雅的解決方案。
自動註冊的對象工廠遵循了開放-封閉原則,新增對象時無需修改原有代碼,僅僅需要擴展即可,徹底地消除了switch-case語句。
實現方法
自動註冊的對象工廠的實現思路如下:
- 提供一個單例工廠對象。
- 工廠註冊對象(保存創建對象的key和構造器)。
- 利用輔助類,在輔助類對象的構造過程中實現目標對象地註冊。
- 利用一個宏來生成輔助對象。
- 在派生類文件中調用這個宏實現自動註冊。
其中,需要注意的是,對象工廠並不直接接存對象,而是對象的構造器,因爲對象工廠不是對象池,是對象的生產者,允許不斷地創建實例,另外,這樣做還實現了延遲創建。另外一個要注意的地方是藉助宏來實現自動註冊,本質上是通過宏來定義了很多全局的靜態變量,而這些靜態變量僅僅是爲了實現自動註冊,並沒有實際的意義。
下面來看看如何用C++11來實現這個自動註冊的對象工廠。
一個單例的對象工廠代碼
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<Message*()>> map_;
};
在C++11中單例的實現非常簡單,返回一個一個靜態局部變量的引用即可,而且這個方法還是線程安全的,因爲C++11中靜態局部變量的初始化是線程安全的。工廠內部有一個map,map的值類型爲一個function,是對象的構造器。
對象工廠的輔助類的代碼
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是工廠類的一個內部模版類,非常簡單,只有一個構造函數,這個構造函數中調用了factory的私有變量map_,並往map_中插入了key和泛型對象的構造器。這裏用到了C++11的一個新特性:內部類可以通過外部類的實例訪問外部類的私有成員,所以register_t可以直接訪問factory的私有變量map_。
自動註冊的代碼
#define REGISTER_MESSAGE_VNAME(T) reg_msg_##T##_
#define REGISTER_MESSAGE(T, key, ...) static factory::register_t<T> REGISTER_MESSAGE_VNAME(T)(key, __VA_ARGS__);
在派生類中調用宏註冊自己:
class Message1 : public Message
{
//……
};
REGISTER_MESSAGE(Message1, "message1");
自動註冊的關鍵是通過一個宏來生成靜態全局的register_t的實例,因爲register_t的實例是用來向工廠註冊目標對象的構造器。所以僅僅需要在派生類中調用這個宏就可以實現自動至註冊了,而無需修改原有代碼。
我們還可以添加智能指針接口,無需讓用戶管理原始指針,甚至讓工廠能創建帶任意參數的對象。
Factory最終的實現
#include <map>
#include <string>
#include <functional>
#include <memory>
#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<Message> produce_unique(const std::string& key)
{
return std::unique_ptr<Message>(produce(key));
}
static std::shared_ptr<Message> produce_shared(const std::string& key)
{
return std::shared_ptr<Message>(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<Message*()>> map_;
};
std::map<std::string, std::function<Message*()>> factory::map_;
#define REGISTER_MESSAGE_VNAME(T) reg_msg_##T##_
#define REGISTER_MESSAGE(T, key, ...) static factory::register_t<T> REGISTER_MESSAGE_VNAME(T)(key, __VA_ARGS__);
示例
class Message
{
public:
virtual ~Message() {}
virtual void foo()
{
}
};
#include "Message.hpp"
#include "MsgFactory.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
delete p;
...
auto p2 = factory::produce_unique("message1");
p2->foo();
}
總結:
使用C++11,僅僅需要幾十行代碼就可以實現一個自動註冊的對象工廠,消除了長長的swithc-case語句,還遵循了開閉原則,簡潔而優雅。
完整的代碼:https://github.com/qicosmos/cosmos/tree/master/self-register-factory