設計模式學習(二)工廠模式——工廠方法模式+登錄檔

paw5zx發表於2024-06-03

目錄
  • 工廠方法模式的瑕疵
  • 登錄檔

工廠方法模式的瑕疵

前一篇筆記中我們介紹了工廠方法模式,示例的類圖如下:

考慮一種情況:現在要在程式執行時,根據外部資源,動態的例項化物件。也就是說在編譯期我們無法知道要例項化的物件的型別。因此在例項化的過程中,就需要加以判斷。

例如,在我的例子中,要根據連線到主機的相機來例項化相機物件,那麼客戶端(使用工廠方法建立例項的一方)使用工廠方法模式建立物件的時候,程式碼可能是這樣:

//執行時確定陣列大小,且確定後不可改變
auto camera_devices_ = std::make_unique<std::shared_ptr<CameraDevice>[]>(onlined_camera_num_);

for(int i = 0; i < onlined_camera_num_; ++i)
{
	std::shared_ptr<CameraDeviceFactory> factory;
	if("Sick" == camera_name[i])    //camera_name[i]中元素是提前獲取的與連線的相機對應的供應商名稱
		factory = std::make_shared<SickCameraFactory>();
	else if("Basler" == camera_name[i])
		factory = std::make_shared<BaslerCameraFactory>();
	else if("Huaray" == camera_name[i])
		factory = std::make_shared<HuarayCameraFactory>();
    camera_devices_[i] = factory->CreateCamera();
}

雖然工廠方法模式遵循了開閉原則,即當有新型別的時候,無需修改現有的程式碼,只需新加產品類和對應工廠類即可。但是對於客戶端來說,當需要例項化的型別數量增加時,就需要新增else if去適配,這使得客戶端程式碼變得冗長且難以維護。

登錄檔

為了解決上面問題,我們可以實現一個型別的登錄檔,允許動態建立物件。這種方法透過將關鍵字對映到建構函式指標,使得可以根據字串名稱動態地例項化物件。

#ifndef Reflection_H
#define Reflection_H

#include <map>
#include <string>

template <typename T, typename... ArgType>
void* CreateInstance(ArgType... args)
{
    return new T(args...);
}

//需要反射的類使用該宏註冊
#ifndef ReflectRegister
#define ReflectRegister(identifier, class_name, ...) \
    static bool __type##class_name = Object::Register(identifier, (void*)CreateInstance<class_name, ##__VA_ARGS__>);
#endif

class Object
{
public:
    template <typename BaseClass, typename... ArgType>
    static BaseClass *CreateObject(const std::string &vendor_name, ArgType... args)
    {
        using CreateFactory = BaseClass *(*)(ArgType...);
        auto& class_map = GetStaticFuncMap();
        auto iter = class_map.find(vendor_name);
        if (iter == class_map.end())
        {
            CRRC_ERROR("class_name not found in map");
            return nullptr;
        }
        else
        {
            CRRC_DEBUG("class_name found in map");
            return reinterpret_cast<CreateFactory>(class_map[vendor_name])(args...);
        }
            
    } 

    //向map中註冊關鍵字和類的建構函式
    static bool Register(const std::string &vendor_name, void *ctor_ptr)
    {
        CRRC_DEBUG("Register class_name:"<<vendor_name);
        GetStaticFuncMap()[vendor_name] = ctor_ptr;
        return true;
    }

private:
    //獲取全域性唯一的map
    //map記錄了關鍵字和類的建構函式的對映關係
    static std::map<std::string, void*>& GetStaticFuncMap()
    {
        static std::map<std::string, void*> class_map_;
        return class_map_;
    }
    
};

#endif //Reflection_H

在具體相機工廠中,我們可以使用ReflectRegister註冊此類(以Basler相機為例,其餘類似):

class BaslerCameraDeviceFactory : public CameraDeviceFactory
{
public:
    std::shared_ptr<CameraDevice> CreateCameraDevice() override
    {
        return std::make_shared<BaslerCameraDevice>();
    }
};

ReflectRegister("Basler", BaslerCameraDeviceFactory);

好了,現在回頭再看客戶端使用工廠方法模式建立物件的程式碼,就可以簡化為:

//執行時確定陣列大小,且確定後不可改變
auto camera_devices_ = std::make_unique<std::shared_ptr<CameraDevice>[]>(onlined_camera_num_);

for(int i = 0; i < onlined_camera_num_; ++i)
{
	auto p_factory = Object::CreateObject<CameraDeviceFactory>(camera_name[i]);//camera_name[i]中元素是提前獲取的與連線的相機對應的供應商名稱
	if (!p_factory)
        continue;
    else
        camera_devices_[i] = p_factory->CreateCameraDevice();
        
    delete p_factory;
}

相關文章