摘要Abstract:本文主要是对《API Design for C++》中Factory Methods章节的翻译,若有不当之处,欢迎指正。

关键字Key Words:C++、Factory Pattern、

一、概述 Overview

工厂方法是创建型模式,允许在不指定需要创建对象类型的情况下创建出对象。本质上来说,工厂方法就是一个通用的构造函数。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)来实现工厂很常见,也很有用。

二、抽象基类 Abstract Base Classes

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

//IRenderer.h
#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;
}

三、简单工厂模式 Simple Factory Example

在复习了抽象基类后,让我们在简单工厂方法中使用它。继续以renderer.h为例,声明创建工厂,创建的对象类型为IRenderer,代码如下所示:

//rendererfactory.h
#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实例,因为抽象基类是不能被实例化的。但是它可以返回派生类的实例。当然,你可以使用字符串作为参数来指定需要创建对象的类型。

假设已经实现了派生自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 &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。但是,他的确不能在运行时添加支持的新的派生类。再专业点,这意味着你的用户不能向系统中添加新的渲染器。通过扩展的对象工厂来解决这些问题。

四、扩展工厂模式 Extensible Factory Example

为了让工派生类从工厂方法中解耦,且允许在运行时添加新的派生类,可以去维护包含类型及与类型创建关联的函数的映射(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 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())
    {
         //  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 renderer
class 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 renderer
class 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 renderer
class 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的一个纯虚函数。

五、结论 Conclusion

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.

Factory Methods相关推荐

  1. Consider static factory methods instead of constructor

    抄袭自<Effective Java ,Second Edition> The normal way for a class to allow a client to obtain an ...

  2. Effective Java - Item 1: Consider static factory methods instead of constructors

    考虑使用静态工厂方法来替代构造方法, 这样的做的好处有四点. 1. 更好的表意 有的构造方法实际上有特殊的含义, 使用静态工厂方法能更好的表达出他的意思. 例如 BigInteger(int, int ...

  3. 我的实用设计模式之Simple Factory,Factory Method和Abstract Factory

    更新1:更新Factory Method部分,说明文中使用"参数化工厂方法"的实现,同时加入经典Factory Method的实现进行比较. 更新2:更新Abstract Fact ...

  4. 扩展LLVM:添加指令、内部函数、类型等

    扩展LLVM:添加指令.内部函数.类型等 Introduction and Warning Adding a new intrinsic function Adding a new instructi ...

  5. python 类中定义类_Python中的动态类定义

    python 类中定义类 Here's a neat Python trick you might just find useful one day. Let's look at how you ca ...

  6. Gradle学习之基础篇

    一.gradle基础概念 Gradle是一个基于Apache Ant和Apache Maven概念的项目自动化构建工具.Gradle抛弃了基于各种繁琐的XML,使用一种基于Groovy的特定领域语言( ...

  7. CSLA.Net 3.0.5 项目管理示例 名值列表基类、只读基类、业务负责人基类

    using System; using System.Data; using System.Data.SqlClient; using Csla; using Csla.Data;namespace ...

  8. java futher多线程_Java多线程系列--“JUC集合”05之 ConcurrentSkipListMap

    概要 本章对Java.util.concurrent包中的ConcurrentSkipListMap类进行详细的介绍.内容包括: ConcurrentSkipListMap介绍 ConcurrentS ...

  9. @classmethod和@staticmethod对初学者的意义? [重复]

    本文翻译自:Meaning of @classmethod and @staticmethod for beginner? [duplicate] This question already has ...

最新文章

  1. 深度学习在图像超分辨率重建中的应用
  2. base64编码 springboot_Spring Boot 中如何实现 HTTP 认证?
  3. vs2010 常见问题处理
  4. main()与_tmain()区别
  5. C++ initializer_list 类型详解
  6. Sring AOP(简记)
  7. 论文浅尝 | WWW2020 - 知识图谱中的实体摘要:算法、评价和应用 (PPT)
  8. Android官方开发文档Training系列课程中文版:通知用户之创建不同导航方式的Activity
  9. Java 为什么数组下标只能为int不能为long?int32位,为何最大值不是2^32 -1 ? java基本类型取值范围
  10. destoon入门实例与常见问题汇总
  11. 微课|玩转Python轻松过二级(3.4节):集合操作与应用
  12. React使用详解(学习笔记)
  13. 中国智慧建造投资前景预测与十四五战略规划建议报告2022年版
  14. RK3399触摸不准,修改drivers中gt9xx.h的cfg
  15. Elasticsearch创建索引别名
  16. 前端性能优化:7.页面渲染优化
  17. Linux学习笔记2—常见指令的使用
  18. 天梯赛题目练习——高速公路超速处罚(附带测试点)
  19. IE 10浏览器使用心得:界面简洁、功能很强大
  20. RabbitMQ-三、Java使用--3、路由选择 (Routing)

热门文章

  1. 标准模式和怪异模式指的是什么?
  2. 【数据分析师求职面试指南】实战技能部分
  3. FusionCharts参数中文说明
  4. RabbitMQ的持久化
  5. python floor() 函数
  6. Thunderbolt4,雷电4与USB4
  7. android锁定屏幕通知_如何在Android锁定屏幕上隐藏敏感通知
  8. 从零开始学架构——架构基础
  9. 906.nethogs安装使用
  10. monkey 测试 ANR 问题 整理分析