设计模式(3)——工厂模式

news/2025/1/14 1:15:41/

文章目录

三、day3

今天学习最常见的设计模式之一——工厂模式工厂模式提供了一种创建对象的最佳方式,在创建对象的时候,不会对客户端暴露创建逻辑,并且通过使用一个共同的接口来创建新的对象。

工厂模式有3种不同的实现方式:

  • 简单工厂模式:又叫做静态工厂方法模式。该模式是通过传⼊相关的类型来返回相应的类,这种方式比较单一,可扩展性相对较差。简单工厂模式看为工厂方法模式的一种特例,两者归为一类。
  • 工厂方法模式:一个抽象产品类,可以派生出多个具体产品类。 一个抽象工厂类,可以派生出多个具体工厂类。每个具体工厂类只能创建一个具体产品类的实例。
  • 抽象工厂模式:多个抽象产品类,每个抽象产品类可以派生出多个具体产品类。一个抽象工厂类,可以派生出多个具体工厂类。 每个具体工厂类可以创建多个具体产品类的实例。

1. 简单工厂模式

使用简单工厂的时候,通常不用创建简单工厂类的类实例,没有创建实例的必要。因此可以把简单工厂类实现成一个工具类,直接使用静态方法就可以了。也就是说简单工厂方法通常是静态的,所以也被称为静态工厂。如果要防止客户端无谓的创造简单工厂实例,还可以把简单工厂的构造方法私有化,并添加一个公共成员函数来创建我们需要的对象。

  • 优点
  1. 实现简单,可扩展。
  2. 实现了对象创建与业务逻辑的分离。
  3. 客户端无需知道所创建的具体产品类和类名。
  • 缺点
  1. 实例化对象的逻辑全部封装在一个工厂类里,每次需求变化都要单独修改工厂类(违反了开闭原则),而且出了异常可能没法正常工作。
  2. 不方便扩展子类。

假如我们想要生产如下产品:

// 人造恶魔果实· 绵羊形态
class SheepSmile
{
public:void transform(){cout << "变成人兽 -- 山羊人形态..." << endl;}void ability(){cout << "将手臂变成绵羊角的招式 -- 巨羊角" << endl;}
};// 人造恶魔果实· 狮子形态
class LionSmile
{
public:void transform(){cout << "变成人兽 -- 狮子人形态..." << endl;}void ability(){cout << "火遁· 豪火球之术..." << endl;}
};// 人造恶魔果实· 蝙蝠形态
class BatSmile
{
public:void transform(){cout << "变成人兽 -- 蝙蝠人形态..." << endl;}void ability(){cout << "声纳引箭之万剑归宗..." << endl;}
};

无论哪一种产品,都有形态变化 transform() 和使用能力 ability() 两种功能。当然,这些产品在生产的过程中可能需要复杂的参数,但在这里这些类构造函数的参数暂时被省略。

如果想要生产这些产品,我们可以创建一个工厂类,然后给这个工厂添加一个工厂函数(通过这个函数来创建我们需要的对象,关于这个函数一般将其称之为工厂函数),我们通过枚举变量控制工厂类要生成哪一类产品,比如 Sheep,Lion或Bat。

enum class Type:char{SHEEP, LION, BAT};
// 恶魔果实工厂类
class SmileFactory
{
public:SmileFactory() {}~SmileFactory() {}void* createSmile(Type type){void* ptr = nullptr;switch (type){case Type::SHEEP:ptr = new SheepSmile;break;case Type::LION:ptr = new LionSmile;break;case Type::BAT:ptr = new BatSmile;break;default:break;}return ptr;}
};int main()
{SmileFactory* factory = new SmileFactory;BatSmile* batObj = (BatSmile*)factory->createSmile(Type::BAT);return 0;
}

我们创建了工厂类 SmileFactory,该类通过工厂函数 createSmile 接受枚举变量创建我们需要的对象实例,并返回 void* 类型的指针。因为每个一对象的指针类型都不同,我们这里统一返回 void* ,我们使用的时候强制转成实际类型即可。

我们也可以通过多态返回基类指针,我们可以直接通过基类指针调用函数,虚函数表会协助我们判断调用的是哪一个对象的虚函数。

#include <iostream>
using namespace std;class AbstractSmile
{
public:virtual void transform() {}virtual void ability() {}virtual ~AbstractSmile() {}
};
// 人造恶魔果实· 绵羊形态
class SheepSmile : public AbstractSmile
{
public:void transform() override{cout << "变成人兽 -- 山羊人形态..." << endl;}void ability() override{cout << "将手臂变成绵羊角的招式 -- 巨羊角" << endl;}
};// 人造恶魔果实· 狮子形态
class LionSmile : public AbstractSmile
{
public:void transform() override{cout << "变成人兽 -- 狮子人形态..." << endl;}void ability() override{cout << "火遁· 豪火球之术..." << endl;}
};class BatSmile : public AbstractSmile
{
public:void transform() override{cout << "变成人兽 -- 蝙蝠人形态..." << endl;}void ability() override{cout << "声纳引箭之万剑归宗..." << endl;}
};// 恶魔果实工厂类
enum class Type:char{SHEEP, LION, BAT};
class SmileFactory
{
public:SmileFactory() {}~SmileFactory() {}AbstractSmile* createSmile(Type type){AbstractSmile* ptr = nullptr;switch (type){case Type::SHEEP:ptr = new SheepSmile;break;case Type::LION:ptr = new LionSmile;break;case Type::BAT:ptr = new BatSmile;break;default:break;}return ptr;}
};int main()
{SmileFactory* factory = new SmileFactory;AbstractSmile* obj = factory->createSmile(Type::BAT);obj->transform();obj->ability();return 0;
}

很简单,因为每个产品都有形态变化 transform() 和使用能力 ability() 两种功能,我们可以将这两个函数抽象化成一个基类 AbstractSmileAbstractSmile 类不仅要将两个功能函数 virtual 化,析构函数也需要加 virtual),我们只需在三个产品类中继承 AbstractSmile,并对两个功能函数 transform() 和使 ability() 进行重写。

在工厂类的工厂函数中,我们通过 switch-case 返回基类指针即可,基类指针指向我们想要指向的产品类,当我们通过基类指针调用派生类功能函数时,编译器通过动态联编和虚函数表协助我们判断调用哪一个函数。

简单工厂模式的 UML 图如下:

在这里插入图片描述

图片来源:https://subingwen.cn/design-patterns/simple-factory/#3-%E5%A6%82%E4%BD%95%E7%94%9F%E4%BA%A7

流程很简单,因为每个产品都有形态变化 transform() 和使用能力 ability() 两种功能,我们可以将这两个函数抽象化成一个基类 AbstractSmileAbstractSmile 类不仅要将两个功能函数 virtual 化,析构函数也需要加 virtual),我们只需在三个产品类中继承 AbstractSmile,并对两个功能函数 transform() 和使 ability() 进行重写。这便生成了三个产品类 SheepSmileLionSmileBatSmile,可以将其看作产品的模具。

当我们想要生产特定产品,比如 sheep 时,我们将枚举变量 sheep 输入至工厂 SmileFactory 中,工厂 SmileFactory 会根据我们给的产品名称(枚举变量 sheep ),使用对应的模具生产产品并返回。

简单工厂模式的缺点

尽管简单工厂模式能生成我们需要的产品,但是如果我们想要生成更多类型不同的产品,超出了枚举变量的范围,那么就需要在工厂函数中添加更多的case,很明显这违背了封闭原则,也就意味着需要基于开放原则来解决这个问题。

2. 工厂模式

工厂模式很好的解决了简单工厂模式的缺点,简单工厂模式是只有一个工厂类,而工厂模式是有很多的工厂类:

  • 一个基类,包含一个虚工厂函数,用于实现多态。
  • 多个子类,重写父类的工厂函数。每个子工厂类负责生产一种恶魔果实,这相当于再次解耦,将工厂类的职责再次拆分、细化,如果要生产新品种的恶魔果实,那么只需要添加对应的工厂类,无需修改原有的代码。

工厂模式包含以下几个角色

  1. 抽象产品(Product):定义了产品的接口,是所有具体产品类的共同父类。
  2. 具体产品(Concrete Product):实现了抽象产品接口的具体产品类,是工厂方法模式所创建的对象。
  3. 抽象工厂(Factory):定义了工厂方法的接口,负责创建抽象产品的对象。
  4. 具体工厂(Concrete Factory):实现了抽象工厂接口,具体工厂类根据具体业务逻辑,创建具体产品的对象。

工厂方法模式的工作流程如下:

  1. 客户端通过调用具体工厂类的工厂方法来创建产品对象。
  2. 具体工厂类根据客户端的请求,调用具体产品类的构造方法创建具体产品对象。
  3. 具体工厂类将创建的具体产品对象返回给客户端。

通过工厂方法模式,客户端与具体产品类解耦,客户端只需关心抽象产品和抽象工厂,具体产品的创建由具体工厂类来完成。这样,当需要引入新的产品时,只需创建一个新的具体产品类和对应的具体工厂类,而不需要修改客户端代码。

工厂方法模式适用于以下情况:

  1. 当一个类无法预知它需要创建的对象的具体类时,可以使用工厂方法模式。
  2. 当一个类希望将对象的创建责任委托给多个子类中的某一个时,可以使用工厂方法模式。
  3. 当一个类需要通过其子类来指定创建对象时,可以使用工厂方法模式。

总结来说,工厂方法模式通过定义一个创建对象的接口,将对象的实例化延迟到子类中去完成,实现了对象的创建与使用的解耦,提高了系统的可扩展性和灵活性。

工厂模式的代码如下:

#include <iostream>
using namespace std;class AbstractSmile
{
public:virtual void transform() = 0;virtual void ability() = 0;virtual ~AbstractSmile() {}
};
// 人造恶魔果实· 绵羊形态
class SheepSmile : public AbstractSmile
{
public:void transform() override{cout << "变成人兽 -- 山羊人形态..." << endl;}void ability() override{cout << "将手臂变成绵羊角的招式 -- 巨羊角" << endl;}
};// 人造恶魔果实· 狮子形态
class LionSmile : public AbstractSmile
{
public:void transform() override{cout << "变成人兽 -- 狮子人形态..." << endl;}void ability() override{cout << "火遁· 豪火球之术..." << endl;}
};class BatSmile : public AbstractSmile
{
public:void transform() override{cout << "变成人兽 -- 蝙蝠人形态..." << endl;}void ability() override{cout << "声纳引箭之万剑归宗..." << endl;}
};// 恶魔果实工厂类
class AbstractFactory
{
public:virtual AbstractSmile* createSmile() = 0;virtual ~AbstractFactory() {}
};class SheepFactory : public AbstractFactory
{
public:AbstractSmile* createSmile() override{return new SheepSmile;}~SheepFactory(){cout << "释放 SheepFactory 类相关的内存资源" << endl;}
};class LionFactory : public AbstractFactory
{
public:// 工厂函数AbstractSmile* createSmile() override{return new LionSmile;}~LionFactory(){cout << "释放 LionFactory 类相关的内存资源" << endl;}};class BatFactory : public AbstractFactory
{
public:// 工厂函数AbstractSmile* createSmile() override{return new BatSmile;}~BatFactory(){cout << "释放 BatFactory 类相关的内存资源" << endl;}
};int main()
{AbstractFactory* factory = new BatFactory;AbstractSmile* obj = factory->createSmile();obj->transform();obj->ability();return 0;
}

工厂模式的UML类图如下所示:

在这里插入图片描述

图片来源:https://subingwen.cn/design-patterns/simple-factory/#3-%E5%A6%82%E4%BD%95%E7%94%9F%E4%BA%A7

上半部分和简单工厂模式一样,因为每个产品都有形态变化 transform() 和使用能力 ability() 两种功能,我们可以将这两个函数抽象化成一个基类 AbstractSmileAbstractSmile 类不仅要将两个功能函数 virtual 化,析构函数也需要加 virtual),我们只需在三个产品类中继承 AbstractSmile,并对两个功能函数 transform() 和使 ability() 进行重写。这便生成了三个产品类 SheepSmileLionSmileBatSmile,可以将其看作产品的模具。

但不同于简单工厂模式,我们这里将工厂类抽象化为了一个接口类 AbstractFactory,其包含两个虚函数,一个虚函数是 createSmile(),用于返回 AbstractSmile 的指针,指针指向生成的产品(多态);另一个函数是析构函数。

当我们需要生成一个新产品时,我们只需要继承这个抽象工厂类 AbstractFactory,然后添加专属于其自身的属性,最后调用重写的 createSmile() 函数,返回一个指向新产品的 AbstractSmile 指针即可,当我们通过 AbstractSmile 指针调用产品的方法时,多态确保我们能够准确指向这个产品的方法。

在我们创建工厂以及产品对象时,必须使用两个抽象类作为指针指向特定产品的工厂和产品模具,比如 AbstractSmileAbstractFactory

  • 优点

符合开放-关闭原则(简称开-闭原则)将创建对象的逻辑与任务交给了工厂类。

  • 缺点

每次新增产品,产品类都需要创建对应工厂类,增加了系统的开销。

3. 抽象工厂模式

抽象工厂模式主要是来解决产品簇问题。

产品簇是什么呢?通俗点来说,造船厂只生产船,但船包括了船体、武器系统、动力系统,这又分为了三个部门,每一个部门又负责很多子产品,这些一系列的产品就是产品簇。如下图:
在这里插入图片描述

在简单工厂模式下,假设武器系统部门现在只有产品1和产品2,如果想要新添加一个产品3,那么需要从工厂函数中添加更多的case,根本的更新工厂,很明显这违背了封闭原则,也就意味着需要基于开放原则来解决这个问题。如下图:

在这里插入图片描述

如果我们想要增加炮弹作为新产品,那么必须将武器部门重组,这样才能有专门负责炮弹的小组进行产品的更新、生产,很明显这违背了开放-封闭(类、模块、函数等可以扩展,但是不可以修改)原则。

工厂模式下,如果要增加一个炮弹产品,相当于新增了两个类,虽然看起来效率和简单工厂模式差不多,但是执行了开发-封闭原则。如下图:

在这里插入图片描述

我们并没有重组武器系统部门,只是添加了两个类,一个是炮弹模具一个是炮弹的属性类,这样我们只是实现了拓展,并没有进行修改。

而如果我们现在想要新增加一个部门,比如动力系统部门,那么相当于增加了8个类,如下图:
在这里插入图片描述

如果我们使用抽象工厂模式,产品簇越多,效果越明显,节省的资源越多,如下图:
在这里插入图片描述

示例代码:

#include <iostream>
#include <string>
using namespace std;// 船体
class ShipBody
{
public:virtual string getShipBody() = 0;virtual ~ShipBody() {}
};class WoodBody : public ShipBody
{
public:string getShipBody() override{return string("用<木材>制作轮船船体...");}
};class IronBody : public ShipBody
{
public:string getShipBody() override{return string("用<钢铁>制作轮船船体...");}
};class MetalBody : public ShipBody
{
public:string getShipBody() override{return string("用<合金>制作轮船船体...");}
};// 武器
class Weapon
{
public:virtual string getWeapon() = 0;virtual ~Weapon() {}
};class Gun : public Weapon
{
public:string getWeapon() override{return string("配备的武器是<枪>...");}
};class Cannon : public Weapon
{
public:string getWeapon() override{return string("配备的武器是<自动机关炮>...");}
};class Laser : public Weapon
{
public:string getWeapon() override{return string("配备的武器是<激光>...");}
};// 动力
class Engine
{
public:virtual string getEngine() = 0;virtual ~Engine() {}
};class Human : public Engine
{
public:string getEngine() override{return string("使用<人力驱动>...");}
};class Diesel : public Engine
{
public:string getEngine() override{return string("使用<内燃机驱动>...");}
};class Nuclear : public Engine
{
public:string getEngine() override{return string("使用<核能驱动>...");}
};// 轮船类
class Ship
{
public:Ship(ShipBody* body, Weapon* weapon, Engine* engine) :m_body(body), m_weapon(weapon), m_engine(engine) {}string getProperty(){string info = m_body->getShipBody() + m_weapon->getWeapon() + m_engine->getEngine();return info;}~Ship() {delete m_body;delete m_engine;delete m_weapon;}
private:ShipBody* m_body = nullptr;Weapon* m_weapon = nullptr;Engine* m_engine = nullptr;
};// 工厂类
class AbstractFactory
{
public:virtual Ship* createShip() = 0;virtual ~AbstractFactory() {}
};class BasicFactory : public AbstractFactory
{
public:Ship* createShip() override{Ship* ship = new Ship(new WoodBody, new Gun, new Human);cout << "<基础型>战船生产完毕, 可以下水啦..." << endl;return ship;}
};class StandardFactory : public AbstractFactory
{
public:Ship* createShip() override{Ship* ship = new Ship(new IronBody, new Cannon, new Diesel);cout << "<标准型>战船生产完毕, 可以下水啦..." << endl;return ship;}
};class UltimateFactory : public AbstractFactory
{
public:Ship* createShip() override{Ship* ship = new Ship(new MetalBody, new Laser, new Nuclear);cout << "<旗舰型>战船生产完毕, 可以下水啦..." << endl;return ship;}
};int main()
{AbstractFactory* factroy = new StandardFactory;Ship* ship = factroy->createShip();cout << ship->getProperty();delete ship;delete factroy;return 0;
}

参考:

简单工厂模式 - 人造恶魔果实工厂1 | 爱编程的大丙

工厂模式 - 人造恶魔果实工厂2 | 爱编程的大丙

抽象工厂模式 - 弗兰奇一家 | 爱编程的大丙

设计模式工厂模式 - 知乎

设计模式工厂模式 - TechNomad - 博客园


http://www.ppmy.cn/news/1562923.html

相关文章

【免费开源】积木JimuBI大屏集成ruoyiVue

JimuBI介绍 JimuBI 是一个JAVA语言的低代码数据可视化大屏BI产品&#xff0c;将大屏、仪表盘、移动面板、图表或页面元素封装为基础组件&#xff0c;无需编写代码即可完成业务需求。 这是JeecgBoot团队出品的另外一款报表产品&#xff0c;积木报表已经成为业内报表首先&#x…

品牌账号矩阵如何打造?来抄作业

在讲究全域营销的当下&#xff0c;目前企业都在各自搭建品牌矩阵号&#xff0c;以提升自己在不同渠道上的影响力。虽然不同平台之间有诸多细节值得深究&#xff0c;但也不妨碍我们先了解如何搭建品牌矩阵。接下来&#xff0c;就让我们一同来了解下该如何搭建。 一、一个主账号 …

初学stm32 --- DAC输出三角波和正弦波

输出三角波实验简要&#xff1a; 1&#xff0c;功能描述 通过DAC1通道1(PA4)输出三角波&#xff0c;然后通过DS100示波器查看波形 2&#xff0c;关闭通道1触发(即自动) TEN1位置0 3&#xff0c;关闭输出缓冲 BOFF1位置1 4&#xff0c;使用12位右对齐模式 将数字量写入DAC_…

SpringBoot项目实战(39)--Beetl网页HTML文件中静态图片及CSS、JS文件的引用和展示

使用Beetl开发网页时&#xff0c;在网页中使用的CSS、JS、图片等静态资源需要进行适当的配置才可以展示。大致的过程如下&#xff1a; &#xff08;1&#xff09;首先Spring Security框架需要允许js、css、图片资源免授权访问。 &#xff08;2&#xff09;网站开发时&#xff0…

【MySQL】count(*)、count(1)和count(列名)区别

问题&#xff1a;用count(*)&#xff0c;count(1)&#xff0c;count(列名)谁好呢? 其实&#xff0c;对于MyISAM引擎的表是没有区别的。这种引擎内部有一计数器在维护着行数。 Innodb引擎的表用count(*),count(1)直接读行数&#xff0c;复杂度是O(n)&#xff0c;因为innodb真的…

【算法】不基于比较的排序(图解)

目录 1.比较器 2.桶排序 2.1.计数排序 2.2.基数排序 3.排序算法的稳定性及其汇总 1.比较器 返回负数的时候&#xff0c;第一个参数排在前面 返回正数的时候&#xff0c;第二个参数排在前面 返回0的时候&#xff0c;谁在前面都无所谓 Override public static void comp…

Spring 中的常用注解

Spring 作为 Java 企业级开发中最广泛使用的框架之一&#xff0c;以其强大的功能和灵活性为开发者提供了高效的开发体验。在 Spring 中&#xff0c;注解&#xff08;Annotation&#xff09;是其核心机制之一&#xff0c;它简化了配置文件的繁琐操作&#xff0c;通过声明的方式实…

机器学习 - 如何理解函数集合中的准确性、召回率、F1分数呢?

在机器学习中&#xff0c;准确性&#xff08;Accuracy&#xff09;、召回率&#xff08;Recall&#xff09;、和F1分数是常用的模型性能评价指标&#xff0c;它们从不同的角度衡量模型的表现。要理解它们&#xff0c;首先需要了解它们的定义和适用场景&#xff1a; 1. 基本概念…