[設計模式] - 工廠模式

一、抽象基類

抽象基類是包含一個或多個純虛成員函數的類。它不是實體類,不能使用new操作符進行實例化,而只能用作一個基類,並由派生類提供純虛方法的實現。e.g.

//renderer.h
class IRenderer
{
public:
	virtual ~IRenderer() {}
	virtual void Render() = 0;
	virtual void SetViewportSize(const std::string& filename) = 0;
};

嚴格講,“純虛方法不提供實現”的說法是不正確的。實際上,可以再.cpp文件中爲純虛方法提供默認實現。例如,可以再render.cpp中爲SetViewportSize()提供一個實現,然後派生類就可以調用IRenderer::SetViewportSize(),但它仍需要顯示地重寫此方法。

抽象基類可用於描述多個類共享的行爲的抽象單元,它指定了所有派生類都必須遵循的契約。在Java中,它被稱爲接口(Java接口只能包含公有方法、靜態變量且不能定義構造函數)。將上述的類命名爲IRender,以I作爲前綴,以此表明它是一個接口類。

二、工廠示例

//rendererfactory.h
#include "renderer.h"
#include <string>
class RendererFactory
{
public:
	IRenderer* CreateRenderer(const std::string& type);
};
//rendererfactory.cpp
#include "rendererfactory.h"
#include "openglrenderer.h"
#include "directxrenderer.h"
#include "mesarenderer.h"

IRenderer* RendererFactory::CreateRenderer(const std::string& type)
{
	if (type == "opengl")
		return new OpenGLRenderer();

	if (type == "directx")
		return new DirectXRenderer();

	if (type == "mesa")
		return new MesaRenderer();

	return NULL;
}

此工廠方法能夠返回IRenderer的3個派生類中的任意一個,返回那個派生類則取決於客戶傳遞的類型字符串。這就允許用戶在運行時決定要創建哪個派生類,而不是在編譯時要求用戶使用正常的構造函數創建類。這是一個巨大的優勢,因爲它意味着能夠基於用戶輸入或根據運行時讀入的配置文件創造不同的類。

另外,還要注意各個具體的派生類的頭文件僅包含在工廠的.cpp文件中。它們不會出現在公有頭文件rendererfactory.h中。實際上,這些都是私有頭文件,且不需要和API一起發佈。因此,用戶永遠看不到不同渲染器的私有細節,他們甚至看不到實現不同渲染器的具體類型。用戶只能通過字符串變量指定渲染器(也可以使用枚舉類型)。

這是一個完全可行的工廠方法。但它的一個潛在缺陷是,包含了可用的派生類的硬編碼信息。如果要爲系統添加新的渲染器,則必須修改rendererfactory.cpp。這個改動不是非常繁重,而且最重要的是它不會影響公有的API。但這也意味着不能爲新的派生類添加運行時支持。換句話說,用戶不能爲系統添加新的渲染器。這些問題可以使用可擴展的對象工廠來解決。

三、擴展工廠示例

//rendererfactory.h
#include "renderer.h"
#include <string>
#include <map>

class RendererFactory
{
public:
	typedef IRenderer* (*CreateCallback)();
	static void RegisterRenderer(const std::string& type, CreateCallback cb);
	static void UnregisterRenderer(const std::string& type);
	static IRenderer* CreateRenderer(const std::string& type);

private:
	typedef std::map<std::string, CreateCallback> CallbackMap;
	static CallbackMap mRenderers;
};
//rendererfactory.cpp
#include "rendererfactory.h"

//在RendererFactory中實例化靜態變量
RendererFactory::CallbackMap RendererFactory::mRenderers;

void RendererFactory::RegisterRenderer(const std::string& type, CreateCallback cb)
{
	mRenderers[type] = cb;
}

void RendererFactory::UnregisterRenderer(const std::string& type)
{
	mRenderers.erase(type);
}

IRenderer* RendererFactory::CreateRenderer(const std::string& type)
{
	CallbackMap::iterator it = mRenderers.find(type);
	if (it != mRenderers.end())
	{
		//調用回調以構造此派生類型的對象
		return (it->second)();
	}
	return NULL;
}
//main.cpp
#include "rendererfactory.h"

class UserRenderer : public IRenderer
{
public:
	~UserRenderer() {}
	void SetViewportSize(int w, int h) {}
	void Render() { std::cout << "User Render" << std::endl; }
	static IRenderer* Create() { return new UserRenderer(); }
};

int main()
{
	//註冊一個新的渲染器
	RendererFactory::RegisterRenderer("user", UserRenderer::Create);

	//爲新渲染器創建一個實例
	IRenderer* r = RendererFactory::CreateRenderer("user");
	r->Render();
	delete r;

	return 0;
}

工廠類維護一個映射,此映射將類型名與創建對象的回調關聯起來。然後就可以允許新的派生類通過一對新的方法調用來實現註冊和註銷。

需要注意的另一個問題是,工廠對象必須保存其狀態信息。因此,最好強制要求任一時刻都只能創建一個工廠對象。這也是爲何多數工廠對象也是單例的原因。

 

參考資料:

《C++ API設計》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章