当前位置: 首页 > article >正文

【第一节】C++设计模式(创建型模式)-工厂模式

目录

前言

一、面向对象的两类对象创建问题

二、解决问题

三、工厂模式代码示例

四、工厂模式的核心功能

五、工厂模式的应用场景

六、工厂模式的实现与结构

七、工厂模式的优缺点

八、工厂模式的扩展与优化

九、总结


前言

        在面向对象系统设计中,开发者常常面临两类典型的对象创建问题。为了解决这些问题,工厂模式(Factory Pattern)应运而生,成为了一种广泛应用的解决方案。本文将详细探讨这两类问题,并分析工厂模式的功能、实现及其在实际开发中的应用。

一、面向对象的两类对象创建问题

(1)抽象基类与多态带来的问题

        为了提高代码的内聚性(Cohesion)和降低耦合性(Coupling),我们通常会抽象出类的公共接口,形成抽象基类或接口。通过声明指向基类的指针来指向实际子类的实现,从而实现多态性。然而,这种做法也带来了以下问题:
        子类名称依赖:客户端代码必须知道具体子类的名称。随着系统复杂度的增加,命名冲突和可读性问题变得难以处理,尤其是当开发者有不同的命名偏好时。
        扩展性与维护困难:每次使用子类时都需要显式地实例化(如 `new ×××`),导致代码重复,扩展性和维护性变差。

(2)父类无法确定具体子类的问题

        在某些情况下,父类并不知道具体要实例化哪一个子类。例如,假设在类 A 中需要使用类 B,而 B 是一个抽象父类。在类 A 中无法确定具体实例化哪一个 B 的子类,但类 A 的子类 D 可以知道。此时,在类 A 中无法直接使用类似 `new ×××` 的语句,因为具体类型未知。

二、解决问题

        我们通常使用工厂模式(Factory Pattern)来解决上述两个问题。在处理第一个问题时,常见的做法是声明一个创建对象的接口,并将对象的创建过程封装起来。此时,工厂类就像一个真正的“生产工厂”,负责生成所需的对象。

        而在第二个问题中,我们需要提供一个对象创建的接口,并在子类中实现具体的创建逻辑,因为只有子类能够决定实例化哪个具体类。 

        第一中情况的 Factory 的结构示意图为:

图1

        图1展示了第一种情况的工厂模式结构示意图。

        这种模式在系统开发中经常被使用,但这并不是工厂模式的最大优势所在(因为这个问题可以通过其他方式解决)。工厂模式不仅仅提供了创建对象的接口,更重要的是它延迟了子类的实例化(即第二个问题)。以下是这种情况的工厂模式结构示意图: 

图2

        图2展示了第二种情况的工厂模式结构示意图。图2中的关键在于,工厂模式的应用并不仅限于封装对象的创建,而是将对象的创建过程放到子类中实现:工厂类只提供对象创建的接口,而具体的实现则由其子类(如 `ConcreteFactory`)完成。这正是图2与图1的主要区别所在。

三、工厂模式代码示例


Product.h

#ifndef PRODUCT_H
#define PRODUCT_H

#include <iostream>

// 抽象基类 Product
class Product {
public:
    virtual ~Product() = 0; // 纯虚析构函数
protected:
    Product() = default; // 默认构造函数,限制为派生类访问
};

// 具体派生类 ConcreteProduct
class ConcreteProduct : public Product {
public:
    ConcreteProduct();
    ~ConcreteProduct() override;
};

#endif // PRODUCT_H


Product.cpp

#include "Product.h"

// 纯虚析构函数的实现
Product::~Product() = default;

// ConcreteProduct 实现
ConcreteProduct::ConcreteProduct() {
    std::cout << "ConcreteProduct created." << std::endl;
}

ConcreteProduct::~ConcreteProduct() {
    std::cout << "ConcreteProduct destroyed." << std::endl;
}


Factory.h

#ifndef FACTORY_H
#define FACTORY_H

class Product;

// 抽象基类 Factory
class Factory {
public:
    virtual ~Factory() = 0; // 纯虚析构函数
    virtual Product* CreateProduct() = 0; // 工厂方法
protected:
    Factory() = default; // 默认构造函数,限制为派生类访问
};

// 具体派生类 ConcreteFactory
class ConcreteFactory : public Factory {
public:
    ConcreteFactory();
    ~ConcreteFactory() override;
    Product* CreateProduct() override;
};

#endif // FACTORY_H


Factory.cpp

#include "Factory.h"
#include "Product.h"
#include <iostream>

// 纯虚析构函数的实现
Factory::~Factory() = default;

// ConcreteFactory 实现
ConcreteFactory::ConcreteFactory() {
    std::cout << "ConcreteFactory created." << std::endl;
}

ConcreteFactory::~ConcreteFactory() {
    std::cout << "ConcreteFactory destroyed." << std::endl;
}

Product* ConcreteFactory::CreateProduct() {
    return new ConcreteProduct();
}


main.cpp

#include "Factory.h"
#include "Product.h"
#include <iostream>

int main() {
    // 创建工厂对象
    Factory* fac = new ConcreteFactory();
    
    // 使用工厂创建产品对象
    Product* p = fac->CreateProduct();
    
    // 释放对象
    delete p;
    delete fac;
    
    return 0;
}

        在示例代码中,工厂模式(Factory Pattern)被用来解决父类无法确定具体要实例化哪一个子类的问题。至于为创建对象提供接口的问题,可以通过在工厂类中附加相应的创建操作来实现,例如添加 `Create***Product()` 方法。

        工厂模式(Factory Pattern)在实际开发中应用非常广泛,尤其是在面向对象系统中,开发者经常面临对象创建的问题:需要创建的类数量非常多。工厂模式通过提供创建对象的接口封装(第一个功能)以及将类的实例化推迟到子类(第二个功能),部分地解决了这些实际问题。一个典型的例子是笔者在开发 VisualCMCS 系统的语义分析模块时,由于需要为文法中的每个非终结符构造一个处理类,因此对象的创建非常频繁。采用工厂模式后,系统的可读性和维护性都变得更加优雅(elegant)。

        然而,工厂模式也带来至少以下两个问题:  
        (1)接口封闭性问题:如果为每一个具体的 `ConcreteProduct` 类的实例化提供一个单独的函数体,那么我们可能不得不在系统中不断添加新的方法来处理这些新创建的 `ConcreteProduct`。这样,工厂接口就难以做到封闭(Close)。虽然可以通过创建一个工厂的子类来利用多态性实现这一点,但这也会导致需要新增一个类作为代价。  
        (2)参数化工厂方法:在实现中,我们可以通过参数化工厂方法,即给 `FactoryMethod()` 传递一个参数来决定创建哪一个具体的 `Product`(实际上,笔者在 VisualCMCS 中也采用了这种方式)。此外,还可以通过模板化来避免第一个问题中的子类创建,具体方法是将具体的 `Product` 类作为模板参数,实现起来也非常简单。  

        可以看出,工厂模式为对象的创建提供了一种优秀的实现策略。然而,工厂模式仅限于处理同一类别的类(即这些类有一个共同的基类)。如果我们需要为不同类别的类提供一个对象创建的接口,那么就需要使用抽象工厂模式(AbstractFactory)了。抽象工厂模式我们下节再讲。

四、工厂模式的核心功能

工厂模式通过以下两个核心功能,解决了上述问题:
(1)定义创建对象的接口,封装对象的创建过程:工厂模式将对象的创建过程抽象化,客户端代码只需依赖工厂接口,而无需关心具体类的实例化细节。
(2)将具体类的实例化延迟到子类:工厂模式允许子类决定实例化哪个具体类,从而将对象创建的决策推迟到运行时。

五、工厂模式的应用场景

(1)封装对象创建
        在第一个问题中,工厂模式通过声明一个创建对象的接口,封装了对象的创建过程。工厂类类似于一个“生产对象”的工厂,客户端代码只需调用工厂接口,而无需直接依赖具体类。

(2)延迟实例化到子类
        在第二个问题中,工厂模式将具体类的实例化延迟到子类。父类只需定义创建对象的接口,而具体实现则由子类完成。这种方式不仅解决了父类无法确定具体子类的问题,还提高了代码的灵活性和可扩展性。

六、工厂模式的实现与结构

(1)简单工厂模式
        简单工厂模式通过一个工厂类封装对象的创建过程。客户端代码只需调用工厂类的方法,即可获取所需对象。然而,这种模式的扩展性较差,新增产品类型时需要修改工厂类。

(2)工厂方法模式
        工厂方法模式将对象的创建延迟到子类。抽象工厂类定义创建对象的接口,具体工厂类负责实例化具体产品。这种方式符合开闭原则,新增产品类型时只需添加新的工厂类,而无需修改现有代码。

(3)抽象工厂模式
        抽象工厂模式用于创建一系列相关或依赖的对象。它为不同产品族提供创建接口,而具体工厂类负责实例化特定产品族中的对象。这种方式适用于需要创建多个不同类型对象的场景。

七、工厂模式的优缺点

(1)优点
        解耦:将对象创建与使用分离,降低了代码的耦合性。
        扩展性:新增产品类型时无需修改现有代码,符合开闭原则。
        灵活性:通过多态将对象创建延迟到运行时,支持动态决策。

(2)缺点
        类数量增加:每新增一个产品类型,可能需要添加新的工厂类,导致类数量膨胀。
        复杂性增加:工厂模式的实现可能增加系统的复杂性,尤其是抽象工厂模式。

八、工厂模式的扩展与优化

(1)参数化工厂方法
        通过为工厂方法传递参数,决定具体创建哪一个产品。这种方式可以减少工厂类的数量,但可能增加工厂方法的复杂性。

(2)模板化工厂
        将具体产品类作为模板参数,避免为每个产品类型创建新的工厂类。这种方式在 C++ 等支持模板的语言中实现较为简单。

(3)抽象工厂模式
        当需要为不同类的产品提供创建接口时,可以使用抽象工厂模式。抽象工厂模式为每个产品族提供一个创建接口,适用于复杂对象创建的场景。

九、总结

        工厂模式是面向对象设计中解决对象创建问题的经典模式。它通过封装对象创建过程和延迟实例化到子类,有效地降低了代码的耦合性,提高了系统的扩展性和灵活性。尽管工厂模式可能增加类的数量和系统的复杂性,但其在解耦和支持变化方面的优势使其在实际开发中得到了广泛应用。对于需要频繁创建对象的系统,工厂模式无疑是一种优雅且高效的解决方案。

        参考学习书籍:设计模式精解-GoF 23 种设计模式解析


http://www.kler.cn/a/553366.html

相关文章:

  • 多模态机器学习火热idea汇总!
  • MYSQL总结(2)
  • 鸿蒙5.0实战案例:图片选择和下载保存案例
  • 卷积神经网络之AlexNet经典神经网络,实现手写数字0~9识别
  • 2025软件测试就业形势剖析:机遇与挑战交织
  • 换服务器需要做的工作(记录一下)
  • [生活杂项][运动教程]自由泳
  • 语音识别中的MFCC特征提取:时频分析如何转化为机器可理解的声学参数?(附完整代码实现)
  • Deepseekv3原理架构中的数学公式,通过高度概括实现快速入门
  • VS Code 如何搭建C/C++开发环境
  • CAN总线常见的错误帧及产生原因
  • 目标检测之YOLO论文简读
  • spring boot知识点3
  • 鸿蒙开发:V2版本装饰器之@Monitor装饰器
  • 代码讲解系列-CV(四)——目标检测初探
  • 【pytest】编写自动化测试用例命名规范README
  • 模拟与高精度
  • 在IDEA的Maven中(同步所有Maven项目)和(重新加载所有Maven项目)的区别
  • 【面试题】杭州士腾科技-面试题汇总
  • mac相关命令