天天看點

工廠方法Factory Methods

<a href="mailto:[email protected]">[email protected]</a>

摘要Abstract:本文主要是對《API Design for C++》中Factory Methods章節的翻譯,若有不當之處,歡迎指正。

關鍵字Key Words:C++、Factory Pattern、

工廠方法是建立型模式,允許在不指定需要建立對象類型的情況下建立出對象。本質上來說,工廠方法就是一個通用的構造函數。C++中的構造函數有以下幾種限制:

l 無傳回值(No return result)。在構造函數中不能傳回一個值。這就意味着:例如當構造失敗時不能傳回一個NULL作為初始化失敗的信号。

l 命名有限制(Constrained naming)。構造函數還是很好識别的,因為它的命名必須與類名一樣。

l 靜态綁定建立的(Statically bound creation)。當建立一個對象時,必須指定一個在編譯時就能确定的類名。如:Foo *f = new Foo(),Foo就是編譯器必須知道的類名。C++的構造函數沒有運作時的動态綁定功能(dynamic binding at run time)。

l 無虛構造函數(No virtual constructors)。在C++中不能聲明虛的構造函數,必須指定在編譯時能确定的類型。編譯器據此為指定的類型配置設定記憶體,然後調用基類的預設構造函數,再調用指定類的構造函數。這就是不能定義構造函數為虛函數的原因。

相反地,工廠方法(factory methods)突破了以上所有的限制。工廠方法的基本功能就是一個可以傳回一個類的執行個體的簡單函數。但是,它通常與繼承組合使用,派生的類可以重載工廠方法以傳回派生類的執行個體。使用抽象基類(Abstract Base Classes)來實作工廠很常見,也很有用。

抽象基類就是包含一個或多個純虛函數(pure virtual methods)的類,這樣的類不是具體類且不能用new來執行個體化。相反地,它是作為其它派生類的基類,由派生類來具體實作那些純虛函數。例如:

#ifndef RENDERER_H

#define RENDERER_H

#include &lt;string&gt;

///

/// An abstract interface for a 3D renderer.

class IRenderer

{

public:

    virtual ~IRenderer() {}

    virtual bool LoadScene(const std::string &amp;filename) = 0;

    virtual void SetViewportSize(int w, int h) = 0;

    virtual void SetCameraPos(double x, double y, double z) = 0;

    virtual void SetLookAt(double x, double y, double z) = 0;

    virtual void Render() = 0;

};

#endif

上述代碼定義了一個抽象基類,描述了一個相當簡單的3D圖形渲染器(renderer)。函數的字尾“=0”聲明這個函數是純虛函數,表示這個函數必須由其派生類來具體實作。

抽象基類是描述了多個類共有的行為的抽象單元,它約定了所有具體派生類必須遵守的合同。在Java中,抽象基類也叫接口(interface),隻是Java的接口隻能是公用的方法(public method),靜态變量,并且不能定義構造函數。将類名IRenderer帶上“I”就是為了表明這個類是接口類(interface class)。

當然,抽象基類中并不是所有的方法都必須是純虛函數,也可以實作一些函數。

當任意一個類有一個或多個虛函數時,通常會把抽象基類的析構函數聲明為虛函數。如下代碼說明了這樣做的重要性:

    // no virtual destructor declared

class RayTracer : public IRenderer

    RayTracer();

    ~RayTracer();

    void Render(); // provide implementation for ABC method

int main(int, char **)

    IRenderer *r = new RayTracer();

    // delete calls IRenderer::~IRenderer, not RayTracer::~RayTracer

    delete r;

}

在複習了抽象基類後,讓我們在簡單工廠方法中使用它。繼續以renderer.h為例,聲明建立工廠,建立的對象類型為IRenderer,代碼如下所示:

#ifndef RENDERERFACTORY_H

#define RENDERERFACTORY_H

#include "renderer.h"

/// A factory object that creates instances of different

/// 3D renderers.

class RendererFactory

    /// Create a new instance of a named 3D renderer.

    /// type can be one of "opengl", "directx", or "mesa"

    IRenderer *CreateRenderer(const std::string &amp;type);

這裡隻聲明了一個工廠方法:它隻是一個普通的函數,傳回值是對象的執行個體。注意到這個方法不能傳回一個指定類型的IRender執行個體,因為抽象基類是不能被執行個體化的。但是它可以傳回派生類的執行個體。當然,你可以使用字元串作為參數來指定需要建立對象的類型。

假設已經實作了派生自IRender的三個具體類:IRenderer::OpenGLRenderer,DirectXRenderer、MesaRenderer。再假設你不想讓使用API的使用者知道可以建立哪些類型:他們必須完全隐藏在API後面。基于這些條件,可以實作工廠方法的程式如下:

// rendererfactory.cpp

#include "rendererfactory.h"

#include "openglrenderer.h"

#include "directxrenderer.h"

#include "mesarenderer.h"

IRenderer *RendererFactory::CreateRenderer(const std::string &amp;type)

    if (type == "opengl")

        return new OpenGLRenderer;

    if (type == "directx")

        return new DirectXRenderer;

    if (type == "mesa")

        return new MesaRenderer;

    return NULL;

這個工廠方法可以傳回IRenderer的三個派生類之一的執行個體,取決于傳入的參數字元串。這就可以讓使用者決定在運作時而不是在編譯時建立哪個派生類,這與普通的構造函數要求一緻。這樣做是有很多好處的,因為它可以根據使用者輸入或根據運作時讀入的配置檔案内容來建立不同的對象。

另外,注意到實作具體派生類的頭檔案隻在rendererfactory.cpp中被包含。它們不出現在rendererfactory.h這個公開的頭檔案中。實際上,這些頭檔案是私有的頭檔案,且不需要與API一起釋出的。這樣使用者就看不到不同的渲染器的私有細節,也看不到具體可以建立哪些不同的渲染器。使用者隻需要通過字元串變量來指定種要建立的渲染器(若你願意,也可用一個枚舉來區分類型)。

此例示範了一個完全可接受的工廠方法。但是,其潛在的缺點就是包含了對可用的各派生類的寫死。若系統需要添加一個新的渲染器,你必須再編輯rendererfactory.cpp。這并不會讓人很煩,重要的是不會影響你提供的公用的API。但是,他的确不能在運作時添加支援的新的派生類。再專業點,這意味着你的使用者不能向系統中添加新的渲染器。通過擴充的對象工廠來解決這些問題。

為了讓工派生類從工廠方法中解耦,且允許在運作時添加新的派生類,可以去維護包含類型及與類型建立關聯的函數的映射(map)來更新一下工廠類。可以通過添加幾個新的函數用來注冊與登出新的派生類。在運作時能注冊新的類允許這種類型的工廠方法模式可用于建立可擴充的接口。

還有個需要注意的事是工廠對象必須儲存狀态,即最好隻有一個工廠對象。這也是工廠對象通常是單件的(singletons)。為了程式的簡單明了,這裡使用靜态變量為例。将所有要點都考慮進來,新的工廠對象代碼如下所示:

#include &lt;map&gt;

/// 3D renderers. New renderers can be dynamically added

/// and removed from the factory object.

    /// The type for the callback that creates an IRenderer instance

    typedef IRenderer *(*CreateCallback)();

    /// Add a new 3D renderer to the system

    static void RegisterRenderer(const std::string &amp;type,

                                 CreateCallback cb);

    /// Remove an existing 3D renderer from the system

    static void UnregisterRenderer(const std::string &amp;type);

    /// Create an instance of a named 3D renderer

    static IRenderer *CreateRenderer(const std::string &amp;type);

private:

    typedef std::map&lt;std::string, CreateCallback&gt; CallbackMap;

    static CallbackMap mRenderers;

為了程式的完整性,将其.cpp檔案中的代碼示例如下:

#include &lt;iostream&gt;

// instantiate the static variable in RendererFactory

RendererFactory::CallbackMap RendererFactory::mRenderers;

void RendererFactory::RegisterRenderer(const std::string &amp;type,

                                       CreateCallback cb)

    mRenderers[type] = cb;

void RendererFactory::UnregisterRenderer(const std::string &amp;type)

    mRenderers.erase(type);

    CallbackMap::iterator it = mRenderers.find(type);

    if (it != mRenderers.end())

    {

        // call the creation callback to construct this derived type

        return (it-&gt;second)();

    }

使用工廠對象建立派生類的方法如下所示:

using std::cout;

using std::endl;

/// An OpenGL-based 3D renderer

class OpenGLRenderer : public IRenderer

    ~OpenGLRenderer() {}

    bool LoadScene(const std::string &amp;filename) { return true; }

    void SetViewportSize(int w, int h) {}

    void SetCameraPos(double x, double y, double z) {}

    void SetLookAt(double x, double y, double z) {}

    void Render() { cout &lt;&lt; "OpenGL Render" &lt;&lt; endl; }

    static IRenderer *Create() { return new OpenGLRenderer; }

/// A DirectX-based 3D renderer

class DirectXRenderer : public IRenderer

    void Render() { cout &lt;&lt; "DirectX Render" &lt;&lt; endl; }

    static IRenderer *Create() { return new DirectXRenderer; }

/// A Mesa-based software 3D renderer

class MesaRenderer : public IRenderer

    void Render() { cout &lt;&lt; "Mesa Render" &lt;&lt; endl; }

    static IRenderer *Create() { return new MesaRenderer; }

    // register the various 3D renderers with the factory object

    RendererFactory::RegisterRenderer("opengl", OpenGLRenderer::Create);

    RendererFactory::RegisterRenderer("directx", DirectXRenderer::Create);

    RendererFactory::RegisterRenderer("mesa", MesaRenderer::Create);

    // create an OpenGL renderer

    IRenderer *ogl = RendererFactory::CreateRenderer("opengl");

    ogl-&gt;Render();

    delete ogl;

    // create a Mesa software renderer

    IRenderer *mesa = RendererFactory::CreateRenderer("mesa");

    mesa-&gt;Render();

    delete mesa;

    // unregister the Mesa renderer

    RendererFactory::UnregisterRenderer("mesa");

    mesa = RendererFactory::CreateRenderer("mesa");

    if (! mesa)

        cout &lt;&lt; "Mesa renderer unregistered" &lt;&lt; endl;

    return 0;

你的API的使用者可以在系統中注冊與登出一個新的渲染器。編譯器将會確定使用者定義的新的渲染器必須實作抽象基類IRenderer的所有抽象接口,即新的渲染器類必須實作抽象基類IRenderer所有的純虛函數。如下代碼示範了使用者如何自定義新的渲染器,在工廠對象中注冊,并叫工廠對象為之建立一個執行個體:

這裡需要注意的一點是我向類UserRenderer中添加了一個Create()函數,這是因為工廠對象的注冊方法需要傳回一個對象的回調函數。這個回調函數不一定必須是抽象基類IRenderer的一部分,它可以是一個自由的函數。但是向抽象基類IRenderer中添加這個函數是一個好習慣,這樣就確定了所有相關功能的一緻性。實際上,為了強調這種約定,可以将Create作為抽象基類IRenderer的一個純虛函數。

Finally, I note that in the extensible factory example given here, a renderer callback has to be

visible to the RegisterRenderer() function at run time. However, this doesn’t mean that you

have to expose the built-in renderers of your API. These can still be hidden either by registering

them within your API initialization routine or by using a hybrid of the simple factory and the extensible

factory, whereby the factory method first checks the type string against a few built-in names.

If none of those match, it then checks for any names that have been registered by the user. This hybrid

approach has the potentially desirable behavior that users cannot override your built-in classes.