一文搞懂设计模式之工厂模式

news2025/1/18 19:11:44

大家好,我是晴天,本周将同大家一起学习设计模式系列的第二篇文章——工厂模式,我们将依次学习简单工厂模式,工厂方法模式和抽象工厂模式。拿好纸和笔,我们现在开始啦~

一文搞懂设计模式之工厂模式.png

前言

我们在进行软件开发的时候,虽然不使用任何设计模式也不耽误搬砖,但是,这样会导致代码可重用性可扩展性可读性可维护性都大打折扣。所以强烈建议使用合适的设计模式进行软件开发。设计模式可以划分成三大类:创建型结构型行为型。本文将先从最基础的创建型模型——工厂模型介绍,工厂模式具体可以划分成简单工厂模式工厂方法模式抽象工厂模式,我们依次来学习一下。

为什么需要工厂模式

我们首先先来看一下下面未使用任何工厂模式的代码,看一下有什么问题?

package main

import "fmt"

// 不使用工厂方法模式
// 实现层
type Car struct {
    name string
}

func (c *Car) Show(name string) {
    if name == "AUDI" {
       c.name = "奥迪"
    } else if name == "BMW" {
       c.name = "宝马"
    } else {
       c.name = "保时捷"
    }
    fmt.Println("我是:" + c.name)
}

func NewCar(name string) Car {
    var c Car
    if name == "BMW" {
       c.name = "宝马"
    } else if name == "AUDI" {
       c.name = "奥迪"
    } else {
       c.name = "保时捷"
    }
    return c
}

// 业务逻辑层
func main() {
    // 定义Car
    var c Car
    // 创建一个具体的AUDICar
    c = NewCar("AUDI")
    c.Show("AUDI") // 我是:奥迪
    c = NewCar("BMW")
    c.Show("BMW") // 我是:宝马
    c = NewCar("CHEVROLET")
    c.Show("CHEVROLET") // 我是:保时捷
}

代码解释:首先在实现层定义了一个Car产品,且有一个Show方法展示自己的品牌,同时写了一个构造函数,用来根据入参来创建具体的Car。在业务逻辑层,创建了一个具体的Car,并通过给NewCar传入不同的参数,创建不同的Car。

存在的问题

  1. 当我们新增车的品牌时,需要对NewCar进行调整和改造,并且也需要对Show方法进行改造,这就涉及到新增产品类别的时候,需要修改类的源代码,这显然违背了开闭原则;同时还会使得Show这个方法变得越来越庞大,这也违背了类的单一职责原则。(对这两个原则不太清楚的小伙伴,可以参考这篇文章一文搞懂设计模式之七大原则)
  2. 实现层和业务逻辑层是高度耦合的,业务逻辑层既要负责创建对象,又要负责使用对象,如果要修改某些逻辑,不仅要修改实现层的代码,业务逻辑层的代码也需要进行改动,会使得创建的逻辑下沉到了业务逻辑层。这显然也是不符合设计模式最基本的一个思想——高内聚,低耦合

为了解决上述问题,我们使用了工厂模式。

简单工厂模式

类图

简单工厂.drawio.png

先来看一下简单工厂的类图,主要分为四个部分:抽象产品类、具体产品类、具体工厂类和main(业务逻辑)。
抽象产品类定义了一个Show方法,需要具体产品类去实现这个Show方法;具体产品类实现Show方法,打印出自己属于哪个具体的产品类型;具体工厂类有一个具体的生产产品的方法CreateComputer,且返回值为抽象产品类Computer(这里一定要返回抽象产品类,方法内部生产具体的产品对象,使用父类指针指向子类指针实现多态

tips:类图中抽象产品类和具体产品类之间的箭头表示继承的意思,具体产品类需要继承抽象产品类的方法,即需要完成该方法的具体实现。工厂类和具体产品类之间的箭头表示依赖的意思,表示CreateComputer方法内部需要用到具体产品类的对象。

代码示例:

package main

import "fmt"

// 练习题:
// 实现简单工厂模式
// 产品类是电脑

// 抽象层
// 抽象电脑,有一个Show方法
type Computer interface {
    Show()
}

// 实现层
// 具体产品类
type Macbook struct {
    Name string
}

func (m *Macbook) Show() {
    fmt.Println(m.Name)
}

type Lenovo struct {
    Name string
}

func (l *Lenovo) Show() {
    fmt.Println(l.Name)
}

// 工厂类
type EasyFactory struct {
}

// 根据入参,生产具体的产品
func (e *EasyFactory) CreateComputer(name string) Computer {
    var c Computer
    if name == "Macbook" {
       c = &Macbook{Name: name}
    } else if name == "Lenovo" {
       c = &Lenovo{Name: name}
    }
    return c
}

// 对产品类符合开闭原则
// 对于工厂类来说,不符合开闭原则,每新增一个产品种类,都需要修改工厂类的方法
// 业务逻辑
func main() {
    // 1.创建一个抽象产品
    var c Computer
    // 2.创建一个具体工厂对象
    var ef = EasyFactory{}
    // 3.调用工厂方法,生产具体产品
    c = ef.CreateComputer("Macbook")
    // 4.调用具体产品的方法
    c.Show()
    // 5.再次调用工厂方法,生产具体产品 (实现多态)
    c = ef.CreateComputer("Lenovo")
    c.Show()
}

代码解释:定义一个Computer抽象产品,定义两个品牌的具体产品类Macbook和Lenovo,定义一个具体工厂类,并且定义一个根据入参创建具体电脑品牌的产品。main函数内部的注释就是按照步骤创建抽象产品、创建工厂、创建具体产品并调用产品方法的过程。

优点

  1. 把具体产品对象的创建和使用进行了解耦合,由工厂实现对具体产品对象的创建,业务逻辑层只需要跟工厂交互,然后使用工厂生产出来的具体产品即可,不用关心对象的创建过程如何。
  2. 将具体产品的初始化工作放到了工厂方法里面,做到了对产品的面向接口编程。

缺点

  1. 对工厂来说,不符合开闭原则,每新增一个具体产品,都需要修改工厂方法。
  2. 工厂的创建方法业务逻辑过于繁重,如果该方法不能顺利执行,将会发生非常严重的问题,整个程序将无法正常运行。

工厂方法模式

工厂方法模式可以理解成:简单工厂模式+开闭原则

类图

工厂方法模式.drawio (3).png

来看一下工厂方法模式的类图,分为五个部分:抽象产品类、具体产品类、抽象工厂类、具体工厂类和main(业务逻辑)。抽象产品类定义了一个Brand方法,需要具体产品类去实现这个Brand方法;具体产品类实现Brand方法,打印出自己属于哪个具体的产品品牌;抽象工厂类定义了ProduceMilk方法,具体工厂类继承抽象工厂类,需要实现ProduceMilk方法,且返回值为抽象产品类Milk(这里一定要返回抽象产品类,方法内部生产具体的产品对象,使用父类指针指向子类指针实现多态

代码示例:

package main

import "fmt"

// 工厂方法就是:简单工厂+开闭原则

// 抽象层
// 抽象产品类
type Milk interface {
    Brand()
}

// 抽象工厂类
type AbstractMilkFactory interface {
    ProduceMilk() Milk
}

// 实现层
// 牛奶类的具体产品类
type MengNiu struct {
    brand string
}

func (m *MengNiu) Brand() {
    fmt.Println("品牌:" + m.brand)
}

// 牛奶类的具体产品类
type YiLi struct {
    brand string
}

func (y *YiLi) Brand() {
    fmt.Println("品牌:" + y.brand)
}

// 工厂类的具体对象
// 蒙牛工厂
type MengNiuFactory struct {
}

func (m *MengNiuFactory) ProduceMilk() Milk {
    var mengNiu MengNiu
    mengNiu.brand = "蒙牛"
    return &mengNiu
}

// 伊利工厂
type YiLiFactory struct {
}

func (y *YiLiFactory) ProduceMilk() Milk {
    var yili YiLi
    yili.brand = "伊利"
    return &yili
}

// 业务逻辑层
func main() {
    // 1.创建蒙牛抽象牛奶工厂
    var mnFacroty AbstractMilkFactory
    // 2.实例化成蒙牛工厂
    mnFacroty = new(MengNiuFactory)
    // 3.生产蒙牛牛奶
    mnMilk := mnFacroty.ProduceMilk()
    // 4.显示牛奶品牌
    mnMilk.Brand()
    // 5.创建伊利抽象牛奶工厂
    var ylFactory AbstractMilkFactory
    // 6.实例化成伊利工厂
    ylFactory = new(YiLiFactory)
    // 7.生产伊利牛奶
    ylMilk := ylFactory.ProduceMilk()
    // 8.显示牛奶品牌
    ylMilk.Brand()
}

代码解释:定义抽象牛奶产品和抽象牛奶工厂,定义蒙牛和伊利两种具体牛奶产品和具体牛奶工厂。main函数(业务逻辑层)只需要跟抽象牛奶产品和抽象牛奶工厂进行交互,符合依赖倒转原则(对这个原则不太清楚的小伙伴,可以参考这篇文章一文搞懂设计模式之七大原则)

优点

  1. 新增一个具体产品时,无需修改产品和工厂的源代码,符合开闭原则
  2. 每个工厂只负责创建一个产品,不需要多个if…else判断创建什么产品,符合单一职责原则
  3. 业务逻辑层只需要跟抽象的产品和抽象的工厂进行交互,符合依赖倒转原则

缺点

  1. 每新增一个具体产品类,都需要新增一个对应的工厂类,代码新增程度是1:1的,增加了程序的复杂程度和代码工作量。
  2. 很难对抽象产品类或者抽象工厂类进行扩展,一旦扩展,所有子类都需要进行修改。

抽象工厂模式

在介绍抽象工厂模式之前先介绍两个概念:产品等级结构产品族

抽象工厂模式.drawio (1).png

产品等级结构:产品等级结构即产品的继承结构。通俗理解:具有相同功能但是来自于不同生产厂商的产品零部件,它们的一个完备集合叫做产品等级结构。

产品族:指由同一个工厂生产的,位于不同产品等级结构中的一组产品。

类图

抽象工厂UML.drawio.png
来看一下抽象工厂的类图,首先明确有哪些抽象类和哪些具体类,抽象类是一个完整的产品等级结构,抽象衣服类、抽象裤子类、抽象鞋类,抽象的工厂类;具体类有中国衣服类、中国裤子类、中国鞋类、日本衣服裤子鞋类,中国工厂类和日本工厂类。中国的衣服裤子鞋组成了中国的产品族,日本衣服裤子鞋组成了日本产品族。中国工厂只依赖于(生产)中国的衣服裤子鞋,日本工厂只依赖于(生产)日本的衣服裤子鞋。

代码示例:

package main

import "fmt"

// 抽象工厂的作用在于不用每新建一个品类,都创建一个工厂,每个工厂生产一整套产品等级结构
// 练习:产品等级结构为
// clothes trousers shoes  衣服 裤子 鞋

// 抽象层
// 产品等级结构为Clothes,trousers,shoes
type Clothes interface {
    PutOn()
}
type Trousers interface {
    PutOn()
}
type Shoes interface {
    TakeOn()
}

// 产品族:生产完整的产品等级结构的工厂
// 一个工厂能生产出完整的产品
type AbsFactory interface {
    CreateClothes() Clothes
    CreateTrousers() Trousers
    CreateShoes() Shoes
}

// 实现层
// 不同产品族的产品等级结构全部创建完成
type ChinaClothes struct {
}

func (c *ChinaClothes) PutOn() {
    fmt.Println("穿上中国衣服")
}

type ChinaTrousers struct {
}

func (c *ChinaTrousers) PutOn() {
    fmt.Println("穿上中国裤子")
}

type ChinaShoes struct {
}

func (c *ChinaShoes) TakeOn() {
    fmt.Println("穿上中国鞋")
}

type JapanClothes struct {
}

func (j *JapanClothes) PutOn() {
    fmt.Println("穿上日本衣服")
}

type JapanTrousers struct {
}

func (j *JapanTrousers) PutOn() {
    fmt.Println("穿上日本裤子")
}

type JapanShoes struct {
}

func (j *JapanShoes) TakeOn() {
    fmt.Println("穿上日本鞋")
}

// 创建中国和日本工厂
type ChinaFactory struct {
}

func (c *ChinaFactory) CreateClothes() Clothes {
    return &ChinaClothes{}
}
func (c *ChinaFactory) CreateTrousers() Trousers {
    return &ChinaTrousers{}
}
func (c *ChinaFactory) CreateShoes() Shoes {
    return &ChinaShoes{}
}

type JapanFactory struct {
}

func (j *JapanFactory) CreateClothes() Clothes {
    return &JapanClothes{}
}
func (j *JapanFactory) CreateTrousers() Trousers {
    return &JapanTrousers{}
}
func (j *JapanFactory) CreateShoes() Shoes {
    return &JapanShoes{}
}

// 业务逻辑层
func main() {
    // 1.定义抽象中国工厂
    var chinaFac AbsFactory
    // 2.实例化中国工厂
    chinaFac = new(ChinaFactory)
    // 3.中国工厂生产中国衣服
    chinaClothes := chinaFac.CreateClothes()
    // 4.中国工厂生产中国裤子
    chinaTrousers := chinaFac.CreateTrousers()
    // 5.中国工厂生产中国鞋
    chinaShoes := chinaFac.CreateShoes()
    // 6.调用中国产品的方法
    chinaClothes.PutOn()
    chinaTrousers.PutOn()
    chinaShoes.TakeOn()
    // 日本工厂同理
    var japanFac AbsFactory
    japanFac = new(JapanFactory)
    japanClothes := japanFac.CreateClothes()
    japanTrousers := japanFac.CreateTrousers()
    japanShoes := japanFac.CreateShoes()
    japanClothes.PutOn()
    japanTrousers.PutOn()
    japanShoes.TakeOn()
}

优点

  1. 拥有工厂模式的优点
  2. 新增一个产品族的时候,只需要新增一个具体的工厂即可,符合开闭原则

缺点

  1. 如果产品等级结构发生变化,对于工厂类来说,抽象工厂类需要新增方法,所有产品族都需要进行修改,不符合开闭原则。
  2. 抽象程度比较高,代码理解起来有一定的困难

总结:

  1. 简单工厂模式:让一个工厂类创建所有的产品,不利于扩展和维护,违背了开闭原则
  2. 工厂方法模式:就是在简单工厂模式的基础上,增加了开闭原则,但是每个工厂类只能创建一种产品,使得代码中类的数量过于庞大
  3. 抽象工厂模式:拥有工厂方法模式的优点,但是对于扩充产品等级结构不友好,违反开闭原则。

写在最后:

感谢大家的阅读,晴天将继续努力,分享更多有趣且实用的主题,如有错误和纰漏,欢迎给予指正。 更多文章敬请关注作者个人公众号 晴天码字。 我们下期不见不散,to be continued…

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1171885.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vector类模拟实现(c++)(学习笔记)

vector 构造函数析构函数[]push_backsize()capacity()reserve()push_back() 迭代器实现非const和const版本 pop_back()resize()insert()***重点erase()***重点再谈构造函数!拷贝构造函数****(重点)运算符重载***(重点)…

详解RSA加密算法 | Java模拟实现RSA算法

目录 一.什么是RSA算法 二.RSA算法的算法原理 算法描述 三.RSA算法安全性 四.RSA算法的速度 五.用java实现RSA算法 一.什么是RSA算法 1976年,Diffie和Hellman在文章“密码学新方向(New Direction in Cryptography)”中首次提出了公开…

arduino - NUCLEO-H723ZG - test

文章目录 arduino - NUCLEO-H723ZG - test概述笔记物理串口软串口备注END arduino - NUCLEO-H723ZG - test 概述 准备向NUCLEO-H723ZG上移植西门子飞达控制的Arduino程序. 先确认一下知识点和效果. 笔记 物理串口 NUCLEO-H723ZG在STM32 Arduino 库中, 只提供了一个串口 Se…

快速了解推荐引擎检索技术

目录 一、推荐引擎和其检索技术 二、推荐引擎的整体架构和工作过程 (一)用户画像 (二)文章画像 (三)推荐算法召回 三、基于内容的召回 (一)召回算法 (二&#xf…

uni-app---- 点击按钮拨打电话功能点击按钮调用高德地图进行导航的功能【安卓app端】

uniapp---- 点击按钮拨打电话功能&&点击按钮调用高德地图进行导航的功能【安卓app端】 先上效果图: 1. 在封装方法的文件夹下新建一个js文件,然后把这些功能进行封装 // 点击按钮拨打电话 export function getActionSheet(phone) {uni.showAct…

【雷达原理】雷达杂波抑制方法

目录 一、杂波及其特点 1.1 什么是杂波? 1.2 杂波的频谱特性 二、动目标显示(MTI)技术 2.1 对消原理 2.2 数字对消器设计 三、MATLAB仿真 3.1 对消效果验证 3.2 代码 一、杂波及其特点 1.1 什么是杂波? 杂波是相对目标回波而言的,…

【Python工具】Panoply介绍及安装步骤

Panoply介绍及安装步骤 1 Panoply介绍2 Panoply安装步骤(Windows)2.1 下载并安装JAVA环境2.2 下载Panoply报错:Error: A JNI error has occurred, please check your installation and try again. 参考 1 Panoply介绍 Panoply是一款由美国国…

【大数据】Apache NiFi 数据同步流程实践

Apache NiFi 数据同步流程实践 1.环境2.Apache NIFI 部署2.1 获取安装包2.2 部署 Apache NIFI 3.NIFI 在手,跟我走!3.1 准备表结构和数据3.2 新建一个 Process Group3.3 新建一个 GenerateTableFetch 组件3.4 配置 GenerateTableFetch 组件3.5 配置 DBCP…

selenium自动化测试入门 —— 设置等待时间

time.sleep(3) 固定等待3秒 driver.implicitly_wait(10) 隐性的等待,对应全局 WebDriverWait( driver, timeout).until(‘有返回值的__call__()方法或函数’) 显性的等待,对应到元素 一、time.sleep(seconds) 固定等待 import time time.sleep(3) #…

【C++那些事儿】类与对象(1)

君兮_的个人主页 即使走的再远,也勿忘启程时的初心 C/C 游戏开发 Hello,米娜桑们,这里是君兮_,我之前看过一套书叫做《明朝那些事儿》,把本来枯燥的历史讲的生动有趣。而C作为一门接近底层的语言,无疑是抽象且难度颇…

10.16nginx负载均衡

nginx正向代理 反向代理 负载均衡 nginx当中有两种代理方式: 七层代理(http协议) 四层代理(基于tcp或udp的流量转发) *七层代理:代理的是http的请求和响应 客户端请求代理服务器,由代理服务…

curl(五)与shell结合的细节

一 curl与shell结合的细节 ① 问题引入 需求: 传递变量以json数据给curl ② 方式1 反斜杠\转义 1、转义内层双引号 --> 了解即可 特点: 可读性低,并且很复杂 2、转义外层单引号 --> 推荐另一种方式: 只转义外层单引号 实质&am…

【马蹄集】—— 百度之星 2023

百度之星 2023 目录 BD202301 公园⭐BD202302 蛋糕划分⭐⭐⭐BD202303 第五维度⭐⭐ BD202301 公园⭐ 难度:钻石    时间限制:1秒    占用内存:64M 题目描述 今天是六一节,小度去公园玩,公园一共 N N N 个景点&am…

使用Gorm进行高级查询

深入探讨GORM的高级查询功能,轻松实现Go中的数据检索 高效的数据检索是每个应用程序性能的核心。GORM,强大的Go对象关系映射库,不仅扩展到基本的CRUD操作,还提供了高级的查询功能。本文是您掌握使用GORM进行高级查询的综合指南。…

计算虚拟化3——I/O设备虚拟化

目录 I/O基本概念 I/O设备与CPU连接图 CPU与I/O设备的交互 访问I/O设备(IO Access) 数据传输(Data Tronhsfer) I/O设备虚拟化技术 软件辅助全虚拟化 半虚拟化 Virtio协议基本概念 Virtqueue讲解 硬件辅助全虚拟化 I/O…

美国航空公司飞行员工会遭受勒索软件攻击

导语:近日,美国航空公司的飞行员工会遭受了一次勒索软件攻击。这次攻击对于全球最大的独立飞行员工会——美国航空公司飞行员协会(APA)造成了一定影响。让我们一起来了解详情。 背景介绍 美国航空公司飞行员协会成立于1963年&…

Bytedance揭秘OpenAI大模型: GPT-3到GPT-4进化路径

文章目录 探秘GPT-3到GPT-4进化之路1、SFT:早期GPT进化的推动者2、RLHF和SFT:编码能力提升的功臣3、代码加入预训练,对推理帮助最大4、“跷跷板”现象 论文地址项目链接Reference GPT-Fathom: Benchmarking Large Language Models to Deciphe…

Python入门:6个好用的Python代码,快来收藏!

文章目录 1.类有两个方法,一个是 new,一个是 init,有什么区别,哪个会先执行呢?2.map 函数返回的对象3.正则表达式中 compile 是否多此一举?4.[[1,2],[3,4],[5,6]]一行代码展开该列表,得出[1,2,3,4,5,6]5.一行代码将字符…

物理内存的关系及分配模式

在分配缓存块的时候,要分两种路径,fast path 和 slow path,也就是快速通道和普通通道。其中 kmem_cache_cpu 就是快速通道,kmem_cache_node 是普通通道。每次分配的时候,要先从 kmem_cache_cpu 进行分配。如果 kmem_ca…

shell脚本代码混淆

文章目录 起因安装 Bashfuscator安装BashfuscatorBashfuscator的使用 起因 很多时候我并不希望自己的shell脚本被别人看到,于是我在想有没有什么玩意可以把代码加密而又正常执行,于是我想到了代码混淆,简单来看一下: 现在我的目…