ICode9

精准搜索请尝试: 精确搜索
首页 > 编程语言> 文章详细

C++ 工厂模式

2022-07-12 10:36:36  阅读:124  来源: 互联网

标签:方法 模式 工厂 C++ 派生类 include type 构造函数


目录

工厂模式解决什么问题?

在C++中,通常,我们用构造函数创建对象。但这种方式存在几个限制:

  • 没有返回值。构造函数不能返回结构,如果发生错误,调用者无法通过返回NULL指针得知。(不过可以在构造函数内抛出异常)
  • 命名限制。C++要求构造函数名与所在类的名字相同,也就是说,如果我们调用了A类构造函数,那么很容易知道是构造A类对象,并且只能构造出A类对象,无法构造B类对象。
  • 静态绑定创建。构造对象时,必须指定编译时能确定的特定类名,构造函数没有运行时动态绑定的概念。例如,不能通过A的构造函数创建B类对象,也不能通过反射创建类对象。
  • 不允许虚构造函数。C++中,不能声明virtual构造函数。必须指定编译时要构造的对象的精确类型,编译器才能为特定类型分片内存并为任意基类调用构造函数。不能在构造函数中调用virtual方法,并期望它们调用派生类的重写版本,因为派生类此时还没有初始化,而调用virtual方法前提是派生类对象已经初始化。

工厂模式绕开了上面的限制。从调用者来看,工厂方法仅是一个普通方法,返回类的实例。不过,工厂模式经常和继承一起使用,可以通过传入不同的参数,以构造不同的派生类对象。

本文主要探讨使用抽象基类(Abstract Base Class)实现工厂模式。


抽象基类

抽象基类是包含一个或多个纯虚函数(pure virtual function)的类。它不能实例化,只能用作基类,由派生类提供纯虚方法的实现。
例,

// renader.h
#include <string>

class IRender // 抽象基类, I作为前缀表明这是一个接口类
{
public:
    virtual ~IRender() {}
    virtual bool LoadScene(cosnt std::string& filename) = 0;  // 加载场景
    virtual void SetViewportSize(int w, int h) = 0;           // 设置视角大小
    virtual void SetCameraPosition(double x, double y, double z) = 0; // 设置相机位置
    virtual void SetLookAt(double x, double y, double z) = 0; // 设置视点
    virtual void Render() = 0;
};

通过为方法添加后缀“=0”,将方法声明为纯虚函数,从而表明该类是一个抽象基类(无法通过new操作费实例化)。

注意:“纯虚方法不提供实现”的说法是错误的,因为可以在.cpp中提供默认实现。但在派生类中仍需要显式重写方法。

抽象基类可以用来描述多个类共享的行为的抽象单元,(从语法层面)指定所有派生类都必须遵守的契约。


工厂方法

工厂模式的意图:定义一个创建对象的接口,让其子类自己决定实例化哪一个类,工厂模式使一个类的实例化延迟到其子类进行。

所谓创建对象的接口,就是指的工厂方法。对用户来说,不再由A a = new A(); 这种方式来创建A类对象,而是由工厂方法来创建。至于具体创建哪个,可以由具体的工厂方法来决定。

其实有两类工厂类:
1)工厂类本身是一个抽象类,由具体的工厂类来创建不同对象,通常是一个具体的工厂只创建一种对象;

2)参数化的工厂方法,由不同参数决定创建何种对象,通常一个工厂创建多种对象。

通过构造函数创建指定对象:

工厂模式创建对象(具体的工厂类):

工厂模式创建对象(参数化的工厂):

简单实现(参数化的工厂)

在renderer.h基础上,实现一个简单的工厂方法,返回IRender类型对象。

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

class RendererFactory
{
public:
    IRenderer* CreateRender(const std::string& type);
};

RendererFactory::CreateRender() 只是返回一个对象实例的普通方法,不能返回具体类型为IRenderer的实例,因为IRenderer是抽象基类无法实例化,不过可以返回派生类实例。方法根据字符串参数决定要创建那个派生类的实例。

假设已经实现了3个IRenderer派生类:OpenGLRenderer、DirectXRenderer和MesaRenderer,并且用户不知道这些类型:API对用户是完全透明的,而且用户不include这3个派生类头文件。
基于此,提供工厂方法的一个实现:

// rendererefactory.cpp
#include "rendererefactory.h"
#include "openglrenderer.h"
#include "directxrenderer.h"
#include "mesarenderer.h"

IRenderer *RendererFactory::CreateRender(const std::string& type)
{
    if (type == "opengl")
        return new OpenGLRenderer();
    else if (type == "directx")
        return new DirectXRenderer();
    else if (type == "mesa")
        return new MesaRenderer();
    return NULL; // 不支持类型
}

工厂方法的意义

1)运行时由用户或配置,决定创建何种类
上面示例中,工厂方法可以返回IRenderere的3个派生类中的任意一个,返回哪个类型取决于客户传递的字符串参数。这也就意味着,允许用户运行时决定要创建哪个派生类,可以根据用户输入或读取的配置文件来创建不同的类,而不是编译时要求用户使用正常的构造函数创建固定的类。

2)隐藏派生类,用户无需关注
各个派生类的头文件仅包含在工厂方法的cpp文件中,不会出现在公有头文件rendererfactory.h中。也就是说,这些私有头文件不需要和API一起发布,用户看不到不同renderer的私有细节。用户只能通过字符串变量指定renderer(当然参数也可以改成其他类型,比如枚举)。

上面工厂方法的缺陷:如果要为系统添加新的渲染器,将不得不修改rendererfactory.cpp。如果要分次添加100个,可能要修改100次!这个问题可以通过可扩展的对象工厂来解决。


扩展工厂

如何将具体的派生类和工程方法解耦,并支持在运行时添加新的派生类?
可以这样修改工厂类:工厂类维护一个映射,此映射将类型名与创建对象的回调关联起来;然后,可以允许新的派生类通过一对新的方法调用来实现注册和注销。

因为工厂对象维护了一个映射,所以是有状态的。因此,最好强制要求任一时刻都只能创建一个工厂对象。这也是为何多数工厂对象时单例的原因。简洁起见,示例使用静态方法和变量,以实现单例工厂:

// rendererefactory.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 renderers_;
};

其相关cpp实现文件:

// rendererfactory.cpp
#include "rendererfactory.h"

// 在RendererFactory中实例化静态变量
RendererFactory::CallbackMap RendererFactory::renderers_;

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

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

IRenderer* RendererFactory::CreateRenderer(const std::string& type)
{
    CallbackMap::iterator it = renderers_.find(type);
    if (it != renderers_.end()) {
        // 调用回调以构造此派生类的对象
        return (it->second)();
    }
    return NULL;
}

用户现在可以在系统中注册、注销新的renderer(渲染器),编译器确保用户的新渲染器遵循IRenderer抽象接口(纯虚函数)。

下面代码展示了用户如何定义自定义渲染器,将其注册到对象工厂,然后通过工厂创建实例。

class UserRenderer : public IRenderer
{
public:
    ~UserRendeer() {}
    bool LoadScene(cosnt std::string& filename) @override { return true; }
    void SetViewportSize(int w, int h) @override {}
    void SetCameraPosition(double x, double y, double z) @override {}
    void SetLookAt() @override {}
    void Render() { std::cout << "User Render" << std::endl; }

    static IRenderer* Create() { return new UserRenderer(); } // 每个自定义renderer必须定义一个Create成员方法, 以向RendererFactory注册该创建自身对象的方法
};

int main()
{
    // 注册一个新的渲染器
    RendererFactory::RegisterRenderer("user", UserRenderer::Create);
    
    // 为新渲染器创建一个实例
    IRenderer* r = RendererFactory::CreateRenderer("user");
    r->Render();
    delete r;

    return 0;
}

相比之前的工厂方法,扩展后的工厂方法RendererFactory有很大改进:添加了注册、注销方法。具体的创建渲染器对象的方法,由具体的渲染器自行负责,提供Create方法创建对象,作为工厂方法创建对象的回调。

通过这种方式,RendererFactory不需要知道具体要创建哪些具体的渲染器,而是由用户来决定。

还需要注意:渲染器的回调必须在运行时对函数RegisterRenderer() 可见,但这不意味着需要暴露API内置渲染器。有两种方式可以隐藏内置渲染器:
1)在API初始化例程中注册这些渲染器,而不是在启动后,由用户运行时决定;
2)混合使用简单工厂模式和扩展工厂模式,工厂方法首先检查类型字符串是否为内置名字,如果不是,再检查类型字符串是否为用户已注册的名字。


参考

[1]Martin Reddy, 刘晓娜, 臧秀涛,等. C++ API设计[M]. 人民邮电出版社, 2013.

标签:方法,模式,工厂,C++,派生类,include,type,构造函数
来源: https://www.cnblogs.com/fortunely/p/16469058.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有