C++11實現一個自動註冊的工廠

實現動機

       工廠方法是最簡單地建立派生類對象的方法,也是很經常使用的,工廠方法內部使用switch-case根據不一樣的key去建立不一樣的派生類對象,下面是一個僞代碼。git

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語句是一個須要解決的問題,而自動註冊的對象工廠則是一個比較優雅的解決方案。github

    自動註冊的對象工廠遵循了開放-封閉原則,新增對象時無需修改原有代碼,僅僅須要擴展便可,完全地消除了switch-case語句。安全

實現方法

       自動註冊的對象工廠的實現思路以下:ide

  1. 提供一個單例工廠對象。
  2. 工廠註冊對象(保存建立對象的key和構造器)。
  3. 利用輔助類,在輔助類對象的構造過程當中實現目標對象地註冊。
  4. 利用一個宏來生成輔助對象。
  5. 在派生類文件中調用這個宏實現自動註冊。

       其中,須要注意的是,對象工廠並不直接保存對象,而是對象的構造器,由於對象工廠不是對象池,是對象的生產者,容許不斷地建立實例,另外,這樣作還實現了延遲建立。另一個要注意的地方是藉助宏來實現自動註冊,本質上是經過宏來定義了不少全局的靜態變量,而這些靜態變量僅僅是爲了實現自動註冊,並無實際的意義。函數

       下面來看看如何用C++11來實現這個自動註冊的對象工廠。spa

一個單例的對象工廠代碼

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__);

在派生類中調用宏註冊本身:code

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 "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(); }

總結

使用C++11,僅僅須要幾十行代碼就能夠實現一個自動註冊的對象工廠,消除了長長的swithc-case語句,還遵循了開閉原則,簡潔而優雅。

完整的代碼:https://github.com/qicosmos/cosmos/tree/master/self-register-factory

若是都是hpp的消息是沒問題的,若是是h和cpp分開的那種,多個cpp包含含靜態變量的頭文件會引發的連接問題,這就把靜態變量幹掉,能夠參考這個實現:

https://github.com/qicosmos/cosmos/blob/master/self-register-factory/MessageFatory1.hpp

相關文章
相關標籤/搜索