条款7中提及工厂method

来源:互联网 发布:7.0采集助手数据库 编辑:程序博客网 时间:2024/05/17 07:48

一、概述 

工厂方法是创建型模式,允许在不指定需要创建对象类型的情况下创建出对象。本质上来说,工厂方法就是一个通用的构造函数。C++中的构造函数有以下几种限制:

1无返回值(No return result)。在构造函数中不能返回一个值。这就意味着:例如当构造失败时不能返回一个NULL作为初始化失败的信号。

2 命名有约束(Constrained naming)。构造函数还是很好识别的,因为它的命名必须与类名一样。

3 静态绑定创建的(Statically bound creation)。当创建一个对象时,必须指定一个在编译时就能确定的类名。如:Foo *f = new Foo(),Foo就是编译器必须知道的类名。C++的构造函数没有运行时的动态绑定功能(dynamic binding at run time)。

4 无虚构造函数(No virtual constructors)。在C++中不能声明虚的构造函数,必须指定在编译时能确定的类型。编译器据此为指定的类型分配内存,然后调用基类的默认构造函数,再调用指定类的构造函数。这就是不能定义构造函数为虚函数的原因。

相反地,工厂方法(factory methods)突破了以上所有的限制。工厂方法的基本功能就是一个可以返回一个类的实例的简单函数。但是,它通常与继承组合使用,派生的类可以重载工厂方法以返回派生类的实例。使用抽象基类(Abstract Base Classes)来实现工厂很常见,也很有用。

二、抽象基类

抽象基类就是包含一个或多个纯虚函数(pure virtual methods)的类,这样的类不是具体类且不能用new来实例化。相反地,它是作为其它派生类的基类,由派生类来具体实现那些纯虚函数。例如:

#ifndef RENDERER_H#define RENDERER_H#include <string>////// An abstract interface for a 3D renderer.///class IRenderer{public:    virtual ~IRenderer() {}    virtual bool LoadScene(const std::string &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)。

当然,抽象基类中并不是所有的方法都必须是纯虚函数,也可以实现一些函数。

当任意一个类有一个或多个虚函数时,通常会把抽象基类的析构函数声明为虚函数。如下代码说明了这样做的重要性:

class IRenderer{    // no virtual destructor declared    virtual void Render() = 0;};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;}

上面代码中r是基类指针,确指向一个继承类,最后删除的时候是通过基类指针。如果基类析构函数不是virtual,则最后继承类成分没有被销毁。如果基类析构函数是virtual,则可以通过基类指针消除整个对象,包括继承类成分。这也就是条款7说明的,“为多态基类声明virtual析构函数”。


三、简单工厂模式

继续以renderer.h为例,声明创建工厂,创建的对象类型为IRenderer,代码如下所示:

#ifndef RENDERERFACTORY_H#define RENDERERFACTORY_H#include "renderer.h"#include <string>////// A factory object that creates instances of different/// 3D renderers.///class RendererFactory{public:    /// Create a new instance of a named 3D renderer.    /// type can be one of "opengl", "directx", or "mesa"    IRenderer *CreateRenderer(const std::string &type);  //注意:工厂函数一般返回的是基类指针};#endif
这里只声明了一个工厂方法:它只是一个普通的函数,返回值是基类指针,一般指向继承类对象。

假设已经实现了派生自IRender的三个具体类:IRenderer::OpenGLRenderer,DirectXRenderer、MesaRenderer。再假设你不想让使用API的用户知道可以创建哪些类型:他们必须完全隐藏在API后面。基于这些条件,可以实现工厂方法的程序如下:

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的三个派生类之一的实例,取决于传入的参数字符串。这就可以让用户决定在运行时而不是在编译时创建哪个派生类,这与普通的构造函数要求一致。这样做是有很多好处的,因为它可以根据用户输入或根据运行时读入的配置文件内容来创建不同的对象。

另外,注意到实现具体派生类的头文件只在rendererfactory.cpp中被包含。它们不出现在rendererfactory.h这个公开的头文件中。实际上,这些头文件是私有的头文件,且不需要与API一起发布的。这样用户就看不到不同的渲染器的私有细节,也看不到具体可以创建哪些不同的渲染器。用户只需要通过字符串变量来指定种要创建的渲染器(若你愿意,也可用一个枚举来区分类型)。

此例演示了一个完全可接受的工厂方法。但是,其潜在的缺点就是包含了对可用的各派生类的硬编码。若系统需要添加一个新的渲染器,你必须再编辑rendererfactory.cpp。这并不会让人很烦,重要的是不会影响你提供的公用的API。但是,他的确不能在运行时添加支持的新的派生类。再专业点,这意味着你的用户不能向系统中添加新的渲染器。通过扩展的对象工厂来解决这些问题。

四、扩展工厂模式

为了让工派生类从工厂方法中解耦,且允许在运行时添加新的派生类,可以去维护包含类型及与类型创建关联的函数的映射(map)来更新一下工厂类。可以通过添加几个新的函数用来注册与注销新的派生类。在运行时能注册新的类允许这种类型的工厂方法模式可用于创建可扩展的接口。

还有个需要注意的事是工厂对象必须保存状态,即最好只有一个工厂对象。这也是工厂对象通常是单件的(singletons)。为了程序的简单明了,这里使用静态变量为例。将所有要点都考虑进来,新的工厂对象代码如下所示:

#ifndef RENDERERFACTORY_H#define RENDERERFACTORY_H#include "renderer.h"#include <string>#include <map>////// A factory object that creates instances of different/// 3D renderers. New renderers can be dynamically added/// and removed from the factory object.///class RendererFactory{public:    /// 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 &type,                                 CreateCallback cb);    /// Remove an existing 3D renderer from the system    static void UnregisterRenderer(const std::string &type);    /// Create an instance of a named 3D renderer    static IRenderer *CreateRenderer(const std::string &type);private:    typedef std::map<std::string, CreateCallback> CallbackMap;    static CallbackMap mRenderers;};#endif
为了程序的完整性,将其.cpp文件中的代码示例如下:

#include "rendererfactory.h"#include <iostream>// instantiate the static variable in RendererFactoryRendererFactory::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())    {        // call the creation callback to construct this derived type        return (it->second)();    }    return NULL;}
使用工厂对象创建派生类的方法如下所示:

#include "rendererfactory.h"#include <iostream>// instantiate the static variable in RendererFactoryRendererFactory::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())    {        // call the creation callback to construct this derived type        return (it->second)();    }    return NULL;}使用工厂对象创建派生类的方法如下所示:#include "rendererfactory.h"#include <iostream>using std::cout;using std::endl;/// An OpenGL-based 3D rendererclass OpenGLRenderer : public IRenderer{public:    ~OpenGLRenderer() {}    bool LoadScene(const std::string &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 << "OpenGL Render" << endl; }    static IRenderer *Create() { return new OpenGLRenderer; }};/// A DirectX-based 3D rendererclass DirectXRenderer : public IRenderer{public:    bool LoadScene(const std::string &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 << "DirectX Render" << endl; }    static IRenderer *Create() { return new DirectXRenderer; }};/// A Mesa-based software 3D rendererclass MesaRenderer : public IRenderer{public:    bool LoadScene(const std::string &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 << "Mesa Render" << endl; }    static IRenderer *Create() { return new MesaRenderer; }};int main(int, char **){    // 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->Render();    delete ogl;    // create a Mesa software renderer    IRenderer *mesa = RendererFactory::CreateRenderer("mesa");    mesa->Render();    delete mesa;    // unregister the Mesa renderer    RendererFactory::UnregisterRenderer("mesa");    mesa = RendererFactory::CreateRenderer("mesa");    if (! mesa)    {        cout << "Mesa renderer unregistered" << endl;    }    return 0;}

你的API的用户可以在系统中注册与注销一个新的渲染器。编译器将会确保用户定义的新的渲染器必须实现抽象基类IRenderer的所有抽象接口,即新的渲染器类必须实现抽象基类IRenderer所有的纯虚函数。如下代码演示了用户如何自定义新的渲染器,在工厂对象中注册,并叫工厂对象为之创建一个实例:

这里需要注意的一点是我向类UserRenderer中添加了一个Create()函数,这是因为工厂对象的注册方法需要返回一个对象的回调函数。这个回调函数不一定必须是抽象基类IRenderer的一部分,它可以是一个自由的函数。但是向抽象基类IRenderer中添加这个函数是一个好习惯,这样就确保了所有相关功能的一致性。实际上,为了强调这种约定,可以将Create作为抽象基类IRenderer的一个纯虚函数。


参考:http://www.cppblog.com/eryar/archive/2013/03/22/198738.html


0 0
原创粉丝点击