1. 程式人生 > >C++11實現一個自動註冊的工廠

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

轉自:https://www.cnblogs.com/qicosmos/p/5090159.html


實現動機

       工廠方法是最簡單地建立派生類物件的方法,也是很常用的,工廠方法內部使用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語句。

實現方法

       自動註冊的物件工廠的實現思路如下:

  1. 提供一個單例工廠物件。
  2. 工廠註冊物件(儲存建立物件的key和構造器)。
  3. 利用輔助類,在輔助類物件的構造過程中實現目標物件地註冊。
  4. 利用一個巨集來生成輔助物件。
  5. 在派生類檔案中呼叫這個巨集實現自動註冊。

       其中,需要注意的是,物件工廠並不直接儲存物件,而是物件的構造器,因為物件工廠不是物件池,是物件的生產者,允許不斷地建立例項,另外,這樣做還實現了延遲建立。另外一個要注意的地方是藉助巨集來實現自動註冊,本質上是通過巨集來定義了很多全域性的靜態變數,而這些靜態變數僅僅是為了實現自動註冊,並沒有實際的意義。

       下面來看看如何用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 "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();
}
複製程式碼