工厂模式有三个Level,你能用Go写到第几层?

2023-10-15 03:40

本文主要是介绍工厂模式有三个Level,你能用Go写到第几层?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

设计模式中的工厂模式是我们编写代码时常用的一种建造型模式,用于创建指定类的实例。

在不使用设计模式的时候,我们是怎么创建类的实例的呢?

别多想,这个问题没坑,就是我们写代码时直接用 new 关键字,直接创建实例。比如 Java 语言里是通过 new 关键字直接调用类的构造方法,完成实例的创建。

class  Person {}Person p1 = new Person();

而像 Go 语言这类,虽说是非面向对象语言,但也提供了创建类型实例指针的 new 方法。

type Person struct{}var p1 Person 
p1 = new(Person)

那既然能直接 new ,有人肯定会问“那为什么还要用设计模式啊?而且听说工厂模式里边还能分出好几种工厂”。解答这个问题并不难,因为我们总是会把一些已经定型的业务处理逻辑抽象成各种项目里的公共类库,这样才能找到抓手,让产品开发形成闭环......。8cb54f748e6dc3c9552659e1e09a209d.png上面的黑话,大家一定不要学,在公司用容易有不好的结果...我这里只是为了提升点喜感,下面用正经话再说一遍。

刚才说到了把一些执行流程明确、但流程细节有差异的业务处理逻辑抽象成公共类库。这时候一组具有相同行为的类都会实现一个固定流程的接口,但是程序里该创建哪个类的实例,提前不知道,只能是根据运行参数动态判断,在加上对于类实例化的过程我们可能需要收敛一下,这样才能保证生产出来的实例能符合我们的预期。

于是乎,聪明的你一定会想到,这时候,我让类库再暴露出一个 NewXXX 之类的方法,这个 NewXXX 方法能够根据条件生产出具体类型的实例返回给业务程序用。如果你能想到这里,恭喜你,这个时候你已经用上工厂模式了。

有时候就是这样,在写代码的时候,不知不觉地就用上设计模式了,虽然你可能还不知道。等我们把设计模式用熟后,自然就能有意识地写出更好的代码。

设计模式里工厂模式一共可以提炼成三类工厂:

  • 简单工厂

  • 工厂方法

  • 抽象工厂

这三类工厂模式的抽象度依次提高,像上面给大家举的例子,你不知不觉用上的就是“简单工厂”这种设计模式,随着这种流程定型的类库越来越复杂,要求的抽象度变高后,我们可能还会用到后面两种工厂模式。这里我就用一些例子,把这三种工厂模式给大家分析一下。

简单工厂

Go 语言没有构造函数一说,所以一般会定义 NewXXX 函数来初始化相关类。NewXXX 函数返回接口时就是简单工厂模式。

考虑一个简单的应用场景,这个应用场景里会提供很多语言的打印机,他们都源于一个 Printer 接口。

// Printer 简单工厂要返回的接口类型
type Printer interface {Print(name string) string
}

程序通过简单工厂向客户端提供需要的语种的打印机。

func NewPrinter(lang string) Printer {switch lang {case "cn":return new(CnPrinter)case "en":return new(EnPrinter)default:return new(CnPrinter)}
}

目前这个场景里我们先提供两个语种的打印机,他们都是 Printer 接口的具体实现类型。

// CnPrinter 是 Printer 接口的实现,它说中文
type CnPrinter struct {}func (*CnPrinter) Print(name string) string {return fmt.Sprintf("你好, %s", name)
}// EnPrinter 是 Printer 接口的实现,它说中文
type EnPrinter struct {}func (*EnPrinter) Print(name string) string {return fmt.Sprintf("Hello, %s", name)
}

这个场景下,工厂、产品接口、具体产品类的关系可以用下面这个图表示。

df87fead57a20d5feedf9d6e5ba31607.png客户端只需要告诉工厂想要哪个语种的打印机产品,工厂就会把产品给返回给客户端。

"示例源码运行Demo
给公众号「网管叨bi叨」发送 go-factory 即可领取"
printer := NewPrinter("en")
fmt.Println(printer.Print("Bob"))

简单工厂模式主要包含3个角色。

  • 简单工厂:是简单工厂模式的核心,负责实现创建所有实例的内部逻辑。工厂类的创建产品类的方法可以被外界直接调用,创建所需的产品对象。

  • 抽象产品:是简单工厂创建的所有对象的抽象父类/接口,负责描述所有实例的行为。

  • 具体产品:是简单工厂模式的创建目标。

简单工厂的优点是,简单,缺点嘛,如果具体产品扩产,就必须修改工厂内部,增加Case,一旦产品过多就会导致简单工厂过于臃肿,为了解决这个问题,才有了下一级别的工厂模式--工厂方法。

工厂方法

工厂方法模式(Factory Method Pattern)又叫作多态性工厂模式,指的是定义一个创建对象的接口,但由实现这个接口的工厂类来决定实例化哪个产品类,工厂方法把类的实例化推迟到子类中进行。

在工厂方法模式中,不再由单一的工厂类生产产品,而是由工厂类的子类实现具体产品的创建。因此,当增加一个产品时,只需增加一个相应的工厂类的子类, 以解决简单工厂生产太多产品时导致其内部代码臃肿(switch … case分支过多)的问题。

下面举个简单的例子来理解工厂方法的设计思想,考虑有这样一个生产计算器的工厂,每类计算器产品都由一个子工厂负责生产。

注意:Go中没有继承,所以这里说的工厂子类,其实是直接实现工厂接口的具体工厂类。

// OperatorFactory 工厂接口,由具体工厂类来实现
type OperatorFactory interface {Create() MathOperator
}// MathOperator 实际产品实现的接口--表示数学运算器应该有哪些行为
type MathOperator interface {SetOperandA(int)SetOperandB(int)ComputeResult() int
}

现在我们假定程序可以生产两类计算器,加法计算器和乘法计算器,也就是在工厂方法模式中,存在两个子类工厂。

//PlusOperatorFactory 是 PlusOperator 加法运算器的工厂类
type PlusOperatorFactory struct{}func (pf *PlusOperatorFactory) Create() MathOperator {return &PlusOperator{BaseOperator: &BaseOperator{},}
}// MultiOperatorFactory 是乘法运算器产品的工厂
type MultiOperatorFactory struct {}func (mf *MultiOperatorFactory) Create() MathOperator{return &MultiOperator{BaseOperator: &BaseOperator{},}
}

这两个子类工厂分别用来生产加法和乘法计算器,

注意:这里为了理解,例子都很简单,真实场景下每个子类工厂创建产品实例的时候是可以放进去复杂逻辑的,不是简单的New一下。

// BaseOperator 是所有 Operator 的基类
// 封装公用方法,因为Go不支持继承,具体Operator类
// 只能组合它来实现类似继承的行为表现。
type BaseOperator struct {operandA, operandB int
}func (o *BaseOperator) SetOperandA(operand int) {o.operandA = operand
}func (o *BaseOperator) SetOperandB(operand int) {o.operandB = operand
}//PlusOperatorFactory 是 PlusOperator 加法运算器的工厂类
type PlusOperatorFactory struct{}func (pf *PlusOperatorFactory) Create() MathOperator {return &PlusOperator{BaseOperator: &BaseOperator{},}
}//PlusOperator 实际的产品类--加法运算器
type PlusOperator struct {*BaseOperator
}//ComputeResult 计算并获取结果
func (p *PlusOperator) ComputeResult() int {return p.operandA + p.operandB
}// MultiOperatorFactory 是乘法运算器产品的工厂
type MultiOperatorFactory struct {}func (mf *MultiOperatorFactory) Create() MathOperator{return &MultiOperator{BaseOperator: &BaseOperator{},}
}// MultiOperator 实际的产品类--乘法运算器
type MultiOperator struct {*BaseOperator
}
func (m *MultiOperator) ComputeResult() int {return m.operandA * m.operandB
}

这个场景下,工厂、产品接口、具体产品类的关系可以用下面这个图表示。

3d349b4a30e84cc7a049a167ff0f7d12.png
类图--工厂方法

测试运行--客户端使用子类工厂创建产品实例。

// 测试运行
示例源码运行Demo
给公众号「网管叨bi叨」发送 go-factory 即可领取
func main() {var factory OperatorFactoryvar mathOp MathOperatorfactory = &PlusOperatorFactory{}mathOp = factory.Create()mathOp.SetOperandB(3)mathOp.SetOperandA(2)fmt.Printf("Plus operation reuslt: %d\n", mathOp.ComputeResult())factory= &MultiOperatorFactory{}mathOp = factory.Create()mathOp.SetOperandB(3)mathOp.SetOperandA(2)fmt.Printf("Multiple operation reuslt: %d\n", mathOp.ComputeResult())
}

工厂方法模式的优点

  • 灵活性增强,对于新产品的创建,只需多写一个相应的工厂类。

  • 典型的解耦框架。高层模块只需要知道产品的抽象类,无须关心其他实现类,满足迪米特法则、依赖倒置原则和里氏替换原则。

工厂方法模式的缺点

  • 类的个数容易过多,增加复杂度。

  • 增加了系统的抽象性和理解难度。

  • 只能生产一种产品,此弊端可使用抽象工厂模式解决。

无论是简单工厂还是工厂方法都只能生产一种产品,如果工厂需要创建生态里的多个产品,就需要更进一步,使用第三级的工厂模式--抽象工厂。

抽象工厂

抽象工厂模式:用于创建一系列相关的或者相互依赖的对象。

为了更清晰地理解工厂方法模式和抽象工厂模式的区别,我们举一个品牌产品生态的例子。

比如智能家居领域多家公司,现在有华为和小米,他们的工厂除了生产我们熟知的手机外,还会生产电视、空调这种家电设备。

假如我们有幸作为他们工厂智能化管理软件的供应商,在软件系统里要对工厂进行抽象,这个时候就不能再用工厂方法这种设计模式了,因为工厂方法只能用来生产一种产品。

我们先看一下使用类图表示的这个抽象工厂抽象多品牌--多产品的形态。

895fd8595dab0b1d7cdfaa87f15fc24d.png
类图--抽象工厂

下面我们用代码简单实现一个抽象工厂,这个工厂能生成智能电视和空调,当然产品的功能在代码里比较简单,就是输出一条相应的信息。

目前抽象工厂有两个实际工厂类一个是华为的工厂,一个是小米的工厂,他们用来实际生产自家的产品设备。

// 示例源码运行Demo
// 给公众号「网管叨bi叨」发送 go-factory 即可领取
type AbstractFactory interface {CreateTelevision() ITelevisionCreateAirConditioner() IAirConditioner
}type ITelevision interface {Watch()
}type IAirConditioner interface {SetTemperature(int)
}type HuaWeiFactory struct{}func (hf *HuaWeiFactory) CreateTelevision() ITelevision {return &HuaWeiTV{}
}
func (hf *HuaWeiFactory) CreateAirConditioner() IAirConditioner {return &HuaWeiAirConditioner{}
}type HuaWeiTV struct{}func (ht *HuaWeiTV) Watch() {fmt.Println("Watch HuaWei TV")
}type HuaWeiAirConditioner struct{}func (ha *HuaWeiAirConditioner) SetTemperature(temp int) {fmt.Printf("HuaWei AirConditioner set temperature to %d ℃\n", temp)
}type MiFactory struct{}func (mf *MiFactory) CreateTelevision() ITelevision {return &MiTV{}
}
func (mf *MiFactory) CreateAirConditioner() IAirConditioner {return &MiAirConditioner{}
}type MiTV struct{}func (mt *MiTV) Watch() {fmt.Println("Watch HuaWei TV")
}type MiAirConditioner struct{}func (ma *MiAirConditioner) SetTemperature(temp int) {fmt.Printf("Mi AirConditioner set temperature to %d ℃\n", temp)
}func main() {var factory AbstractFactoryvar tv ITelevisionvar air IAirConditionerfactory = &HuaWeiFactory{}tv = factory.CreateTelevision()air = factory.CreateAirConditioner()tv.Watch()air.SetTemperature(25)factory = &MiFactory{}tv = factory.CreateTelevision()air = factory.CreateAirConditioner()tv.Watch()air.SetTemperature(26)
}

同样抽象工厂也具备工厂方法把产品的创建推迟到工厂子类去做的特性,假如未来加入了 VIVO 的产品,我们就可以通过再创建 VIVO 工厂子类来扩展。

对于抽象工厂我们可以总结以下几点:

  • 当系统所提供的工厂所需生产的具体产品并不是一个简单的对象,而是多个位于不同产品等级结构中属于不同类型的具体产品时需要使用抽象工厂模式。

  • 抽象工厂模式是所有形式的工厂模式中最为抽象和最具一般性的一种形态。

  • 抽象工厂模式与工厂方法模式最大的区别在于,工厂方法模式针对的是一个产品等级结构,而抽象工厂模式则需要面对多个产品等级结构,一个工厂等级结构可以负责多个不同产品等级结构中的产品对象的创建 。

  • 当一个工厂等级结构可以创建出分属于不同产品等级结构的一个产品族中的所有对象时,抽象工厂模式比工厂方法模式更为简单、有效率。

抽象工厂模式的优点

  • 当需要产品族时,抽象工厂可以保证客户端始终只使用同一个产品的产品族。

  • 抽象工厂增强了程序的可扩展性,对于新产品族的增加,只需实现一个新的具体工厂即可,不需要对已有代码进行修改,符合开闭原则。

抽象工厂模式的缺点

  • 规定了所有可能被创建的产品集合,产品族中扩展新的产品困难,需要修改抽象工厂的接口。

  • 增加了系统的抽象性和理解难度。

最后

我们用几个较为简单的例子和大家一起学习了下三个工厂模式各自的场景和优缺点,实际使用的时候项目一开始需求还没那么明确的时候推荐还是先用简单工厂,等我们业务理解更透彻后如果确实需要再升级到工厂方法也不迟。

抽象工厂也是,如果确定引入产品生态的概念才能更好地进行领域建模,再开始使用抽象工厂也不迟。

这篇关于工厂模式有三个Level,你能用Go写到第几层?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/215117

相关文章

在JS中的设计模式的单例模式、策略模式、代理模式、原型模式浅讲

1. 单例模式(Singleton Pattern) 确保一个类只有一个实例,并提供一个全局访问点。 示例代码: class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}Singleton.instance = this;this.data = [];}addData(value)

工厂ERP管理系统实现源码(JAVA)

工厂进销存管理系统是一个集采购管理、仓库管理、生产管理和销售管理于一体的综合解决方案。该系统旨在帮助企业优化流程、提高效率、降低成本,并实时掌握各环节的运营状况。 在采购管理方面,系统能够处理采购订单、供应商管理和采购入库等流程,确保采购过程的透明和高效。仓库管理方面,实现库存的精准管理,包括入库、出库、盘点等操作,确保库存数据的准确性和实时性。 生产管理模块则涵盖了生产计划制定、物料需求计划、

Go Playground 在线编程环境

For all examples in this and the next chapter, we will use Go Playground. Go Playground represents a web service that can run programs written in Go. It can be opened in a web browser using the follow

go基础知识归纳总结

无缓冲的 channel 和有缓冲的 channel 的区别? 在 Go 语言中,channel 是用来在 goroutines 之间传递数据的主要机制。它们有两种类型:无缓冲的 channel 和有缓冲的 channel。 无缓冲的 channel 行为:无缓冲的 channel 是一种同步的通信方式,发送和接收必须同时发生。如果一个 goroutine 试图通过无缓冲 channel

如何确定 Go 语言中 HTTP 连接池的最佳参数?

确定 Go 语言中 HTTP 连接池的最佳参数可以通过以下几种方式: 一、分析应用场景和需求 并发请求量: 确定应用程序在特定时间段内可能同时发起的 HTTP 请求数量。如果并发请求量很高,需要设置较大的连接池参数以满足需求。例如,对于一个高并发的 Web 服务,可能同时有数百个请求在处理,此时需要较大的连接池大小。可以通过压力测试工具模拟高并发场景,观察系统在不同并发请求下的性能表现,从而

模版方法模式template method

学习笔记,原文链接 https://refactoringguru.cn/design-patterns/template-method 超类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤。 上层接口有默认实现的方法和子类需要自己实现的方法

【iOS】MVC模式

MVC模式 MVC模式MVC模式demo MVC模式 MVC模式全称为model(模型)view(视图)controller(控制器),他分为三个不同的层分别负责不同的职责。 View:该层用于存放视图,该层中我们可以对页面及控件进行布局。Model:模型一般都拥有很好的可复用性,在该层中,我们可以统一管理一些数据。Controlller:该层充当一个CPU的功能,即该应用程序

迭代器模式iterator

学习笔记,原文链接 https://refactoringguru.cn/design-patterns/iterator 不暴露集合底层表现形式 (列表、 栈和树等) 的情况下遍历集合中所有的元素

《x86汇编语言:从实模式到保护模式》视频来了

《x86汇编语言:从实模式到保护模式》视频来了 很多朋友留言,说我的专栏《x86汇编语言:从实模式到保护模式》写得很详细,还有的朋友希望我能写得更细,最好是覆盖全书的所有章节。 毕竟我不是作者,只有作者的解读才是最权威的。 当初我学习这本书的时候,只能靠自己摸索,网上搜不到什么好资源。 如果你正在学这本书或者汇编语言,那你有福气了。 本书作者李忠老师,以此书为蓝本,录制了全套视频。 试

利用命令模式构建高效的手游后端架构

在现代手游开发中,后端架构的设计对于支持高并发、快速迭代和复杂游戏逻辑至关重要。命令模式作为一种行为设计模式,可以有效地解耦请求的发起者与接收者,提升系统的可维护性和扩展性。本文将深入探讨如何利用命令模式构建一个强大且灵活的手游后端架构。 1. 命令模式的概念与优势 命令模式通过将请求封装为对象,使得请求的发起者和接收者之间的耦合度降低。这种模式的主要优势包括: 解耦请求发起者与处理者