> 文档中心 > C++ | 设计模式 :基于自实现的智能指针unique_ptr实现工厂方法模式 | 工厂模式的优缺点总结

C++ | 设计模式 :基于自实现的智能指针unique_ptr实现工厂方法模式 | 工厂模式的优缺点总结


前言

在上篇文章中,简单模拟了唯一性智能指针,本篇文章将用my_unique_ptr实现工厂方法模式

目录

  • 前言
  • 一、自实现智能指针unique_ptr
    • 智能指针代码
  • 二、工厂方法模式
    • 1.工厂方法模式概述
    • 2.工厂方法模式定义
    • 3.工厂方法模式的角色(组成)
  • 三、使用实例
    • 题目
    • 1.抽象产品:日志记录器接口
    • 2.具体产品:数据库日志记录器
    • 3.具体产品:文件日志记录器
    • 4.抽象工厂
    • 5.具体工厂:数据库日志记录器工厂
    • 6. 具体工厂:文件日志记录器工厂
    • 客户端示例
    • 逐步讲解客户端调用过程
  • 四、工厂方法模式总结
    • 1.优点
    • 2. 缺点
    • 3.适用场景

一、自实现智能指针unique_ptr

原文链接:简单模拟unique_ptr

智能指针代码

template<typename _Ty>class myDeletor{public:myDeletor() = default;void operator()(_Ty* ptr) const{if (ptr != nullptr){delete ptr;}}};template<typename _Ty>class myDeletor<_Ty[]>{public:myDeletor() = default;void operator()(_Ty* ptr) const{if (ptr != nullptr){delete[]ptr;}}};// my_unique_ptrtemplate<typename _Ty, typename _Dx = myDeletor<_Ty> >class my_unique_ptr{public:using pointer = _Ty*;using element_type = _Ty;using delete_type = _Dx;private:pointer _Ptr;delete_type _myDeletor;public:my_unique_ptr(const my_unique_ptr& _Y) = delete;my_unique_ptr operator =(const my_unique_ptr& _Y) = delete;my_unique_ptr(pointer _P = nullptr): _Ptr(_P){cout << "my_unique_ptr: " << this << endl;}~my_unique_ptr() {if (_Ptr != nullptr) {_myDeletor(_Ptr);_Ptr = nullptr;}cout << "~my_unique_ptr: " << this << endl;}_Dx& get_deletor() {return _myDeletor;}const _Dx& get_deletor() const {return _myDeletor;}pointer get_pointer() const {return _Ptr;}operator bool() {return _Ptr != nullptr;}_Ty& operator*() const {return *_Ptr;}pointer operator->() const {return &**this;}pointer release() {pointer old = _Ptr;_Ptr = nullptr;return old;}void reset(pointer _P = nullptr) {pointer old = _Ptr;_Ptr = _P;if (old != nullptr) {_myDeletor(old);// get_deletor()(old);}}void Swap(my_unique_ptr _Y) {std::swap(_Ptr, _Y._Ptr);std::swap(_myDeletor, _Y._myDeletor);}my_unique_ptr(my_unique_ptr&& _Y) {_Ptr = _Y._Ptr;_Y = nullptr;}my_unique_ptr& operator=(my_unique_ptr&& _Y) {if (this == &_Y) {return *this;}reset(_Y.release());return *this;}};// arraytemplate<typename _Ty, typename _Dx>class my_unique_ptr<_Ty[], _Dx>{public:using pointer = _Ty*;using element_type = _Ty;using delete_type = _Dx;private:pointer _Ptr;delete_type _myDeletor;public:my_unique_ptr(const my_unique_ptr& _Y) = delete;my_unique_ptr operator =(const my_unique_ptr& _Y) = delete;my_unique_ptr(pointer _P = nullptr): _Ptr(_P){cout << "my_unique_ptr: " << this << endl;}~my_unique_ptr() {if (_Ptr != nullptr) {_myDeletor(_Ptr);_Ptr = nullptr;}cout << "~my_unique_ptr: " << this << endl;}_Dx& get_deletor() {return _myDeletor;}const _Dx& get_deletor() const {return _myDeletor;}pointer get_pointer() const {return _Ptr;}operator bool() {return _Ptr != nullptr;}_Ty& operator*() const {return *_Ptr;}pointer operator->() const {return &**this;}pointer release() {pointer old = _Ptr;_Ptr = nullptr;return old;}void reset(pointer _P = nullptr) {pointer old = _Ptr;_Ptr = _P;if (old != nullptr) {_myDeletor(old);// get_deletor()(old);}}void Swap(my_unique_ptr _Y) {std::swap(_Ptr, _Y._Ptr);std::swap(_myDeletor, _Y._myDeletor);}my_unique_ptr(my_unique_ptr&& _Y) {_Ptr = _Y._Ptr;_Y = nullptr;}my_unique_ptr& operator=(my_unique_ptr&& _Y) {if (this == &_Y) {return *this;}reset(_Y.release());return *this;}_Ty& operator[](size_t _Idx) const {return _Ptr[_Idx];}};template<typename _Ty, typename ... _Type>my_unique_ptr<_Ty> my_make_unique(_Type&& ... _args){return my_unique_ptr<_Ty>(new _Ty(_args...));}

二、工厂方法模式

简单工厂模式:C++实现简单工厂模式

1.工厂方法模式概述

在简单工厂模式中只提供一个工厂类,该工厂类处于对产品类进行实例化的中心位置它需要知道每一个产品对象的创建细节,并决定何时实例化哪一个产品类。
简单工厂模式最大的缺点是当有新产品要加入到系统中时,必须修改工厂类,需要在其中加入必要的业务逻辑,这违背了“开闭原则”。
此外,在简单工厂模式中,所有的产品都由同一个工厂创建,工厂类职责较重,业务逻辑较为复杂,具体产品与工厂类之间的耦合度高,严重影响了系统的灵活性和扩展性,而工厂方法模式则可以很好地解决这一问题。

在工厂方法模式中,我们不再提供一个统一的工厂类来创建所有的产品对象,而是针对不同的产品提供不同的工厂,系统提供一个与产品等级结构对应的工厂等级结构。

2.工厂方法模式定义

工厂方法模式(Factory Method Pattern) :定义一个用于创建对象的接口,让子类决定将哪一个类实例化。工厂方法模式让一个类的实例化延迟到其子类。工厂方法模式又简称为工厂模式(Factory Pattern),又可称作虚拟构造器模式(Virtual Constructor Pattern)或多态工厂模式(Polymorphic Factory Pattern)。

工厂方法模式提供一个抽象工厂接口来声明抽象工厂方法,而由其子类来具体实现工厂方法,创建具体的产品对象。

3.工厂方法模式的角色(组成)

在工厂方法模式结构图中包含如下几个角色:

  1. 抽象产品(Product) :
    关系:具体产品类型的基类;
    作用:描述具体产品的公共接口;
  2. 具体产品(Concrete Product) :
    关系:抽象产品类型的派生类(子类),工厂类创建的目标类(具体工厂和具体产品之间一一对应);
    作用:描述生产的具体产品
  3. 抽象工厂(Factory) :
    关系:具体工厂的基类;
    作用:声明了工厂方法(Factory Method),用于返回一个产品。抽象工厂是工厂方法模式的核心,所有创建对象的工厂类都必须实现该接口。
    常见代码:
struct Factory{virtual Product* factoryMethod();};
  1. 具体工厂(Concrete Factory) :
    关系:抽象工厂的派生类;被外界调用;
    作用:描述具体工厂;实现 FactoryMethod (创建产品的实例)工厂方法,并可由客户端调用,返回一个具体产品类的实例。

与简单工厂模式相比,工厂方法模式最重要的区别是引入了抽象工厂角色,抽象工厂可以是接口,也可以是抽象类或具体类。

三、使用实例

题目

sunny软件公司要开发一个系统运行日志记录器,该纪录器可以通过多种途径保存系统的运行日志。

设计图示如下

在这里插入图片描述

在这里插入图片描述

1.抽象产品:日志记录器接口

拥有写日志的虚方法,需要派生类来重写。

// 日志记录器接口:抽象产品struct Logger{virtual void writeLog() = 0;Logger() { cout << "日志记录器接口" << endl; }virtual ~Logger() { cout << "~Logger " << endl; }};

2.具体产品:数据库日志记录器

// 数据库日志记录器:具体产品class DatabaseLogger : public Logger{public:DatabaseLogger() { cout << "DatabaseLogger" << endl; }~DatabaseLogger() { cout << "~DatabaseLogger" << endl; }void writeLog(){cout << "数据库日志记录。" << endl;}};

3.具体产品:文件日志记录器

// 文件日志记录器:具体产品class FileLogger : public Logger{public:FileLogger() { cout << "FileLogger" << endl; }~FileLogger() { cout << "~FileLogger" << endl; }void writeLog(){cout << "文件日志记录。" << endl;}};

4.抽象工厂

返回值为抽象产品类的智能指针

// 日志记录器工厂接口:抽象工厂struct LoggerFactory{virtual my_unique_ptr<Logger> createLogger() = 0;LoggerFactory() { cout << "LoggerFactory" << endl; }virtual ~LoggerFactory() { cout << "~LoggerFactory" << endl; }};

5.具体工厂:数据库日志记录器工厂

重写了抽象工厂的虚方法,会返回一个向堆区申请的数据库产品。

// 数据库日志记录器工厂类 : 具体工厂class DatabaseLoggerFactory : public LoggerFactory{public:DatabaseLoggerFactory() { cout << "DatabaseLoggerFactory" << endl; }~DatabaseLoggerFactory() { cout << "~DatabaseLoggerFactory" << endl; }my_unique_ptr<Logger> createLogger(){return my_unique_ptr<Logger>(new DatabaseLogger());// return my_make_unique();}};

6. 具体工厂:文件日志记录器工厂

会返回一个向堆区申请的文件日志产品

// 文件日志记录器工厂类 : 具体工厂class FileLoggerFactory : public LoggerFactory{public:FileLoggerFactory() { cout << "FileLoggerFactory" << endl; }~FileLoggerFactory() { cout << "~FileLoggerFactory" << endl; }my_unique_ptr<Logger> createLogger(){return my_unique_ptr<Logger>(new FileLogger());}};

客户端示例

int main(void){my_unique_ptr<LoggerFactory> factory(new FileLoggerFactory());my_unique_ptr <Logger> logger = factory->createLogger();logger->writeLog();factory.reset(new DatabaseLoggerFactory());logger = factory->createLogger();logger->writeLog();return 0;}

运行结果:
在这里插入图片描述

逐步讲解客户端调用过程

  1. 创建一个指向抽象工厂的智能指针(factory),这个抽象工厂指针指向堆区申请的文件工厂,且虚表指针指向文件工厂的虚表。对应运行结果1到3行。

在这里插入图片描述

  1. 创建一个指向日志记录器的智能指针(logger),指向factory的createLogger所返回的对象(FileLogger)。因为factory的虚表指向FileLoggerFactory的虚表,所以会返回FileLogger。对应运行结果4到6行。

在这里插入图片描述

  1. 调用Logger的writeLog(),那么根据上图,会调用FileLogger::writeLog。对应运行结果第7行。

  2. factory这个指针调用reset(),让指针指向其他对象,那么构造过程和第一张图同理,由于指向了另一个对象,所以要析构之前申请的对象。对应运行结果的8到12行。

  3. 和第二步同理,不过中间调用了移动构造函数,将资源进行了转移。对应运行结果13到18行。

  4. 调用 DatabaseLogger::writeLog,对应运行结果第19行。

  5. 程序结束,析构所有对象。

由于是使用智能指针实现,我们会对堆区的资源进行自动化管理,不用担心遗忘释放等问题,这在业务程序中,比使用裸指针更方便。

四、工厂方法模式总结

工厂方法模式是简单工厂模式的延伸,它继承了简单工厂模式的优点,同时还弥补了简单工厂模式的不足。工厂方法模式是使用频率最高的设计模式之一,是很多开源框架和API类库的核心模式。

在工厂方法模式中,核心的工厂类不再负责所有产品的创建,而是将具体创建工作交给子类去做。这个核心类仅仅负责给出具体工厂必须实现的接口,而不负责哪一个产品类被实例化这种细节,这使得工厂方法模式可以允许系统在不修改工厂角色的情况下引进新产品。

1.优点

  1. 在工厂方法模式中,工厂方法用来创建客户所需要的产品,同时还向客户隐藏了哪种具体产品类将被实例化这一细节,用户只需要关心所需产品对应的工厂,无须关心创建细节,甚至无须知道具体产品类的类名。
  2. 基于工厂角色和产品角色的多态性设计是工厂方法模式的关键。它能够让工厂可以自主确定创建何种产品对象,而如何创建这个对象的细节则完全封装在具体工厂内部。工厂方法模式之所以又被称为多态工厂模式,就正是因为所有的具体工厂类都具有同一抽象父类。
  3. 使用工厂方法模式的另一个优点是在系统中加入新产品时,无须修改抽象工厂和抽象产品提供的接口,无须修改客户端,也无须修改其他的具体工厂和具体产品,而只要添加一个具体工厂和具体产品就可以了,这样,系统的可扩展性也就变得非常好,完全符合“开闭原则”。

2. 缺点

工厂方法模式的主要缺点如下:

  1. 在添加新产品时,需要编写新的具体产品类,而且还要提供与之对应的具体工厂类,系统中类的个数将成对增加,在一定程度上增加了系统的复杂度,有更多的类需要编译和运行,会给系统带来一些额外的开销。
  2. 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度,且在实现时可能需要用到DOM、反射等技术,增加了系统的实现难度。

3.适用场景

在以下情况下可以考虑使用工厂方法模式:

  1. 客户端不知道它所需要的对象的类。在工厂方法模式中,客户端不需要知道具体产品类的类名,只需要知道所对应的工厂即可,具体的产品对象由具体工厂类创建,可将具体工厂类的类名存储在配置文件或数据库中。
  2. 抽象工厂类通过其子类来指定创建哪个对象。在工厂方法模式中,对于抽象工厂类只需要提供一个创建产品的接口,而由其子类来确定具体要创建的对象,利用面向对象的多态性和里氏代换原则,在程序运行时,子类对象将覆盖父类对象,从而使得系统更容易扩展。