工厂模式分为三个层次。你能写几级?
我报名了金石计划的第一个挑战——瓜分10万奖池,这是我的第n篇文章, 点击查看活动详情
设计模式中的工厂模式是我们编写代码创建指定类的实例时常用的一种构造模式。
我们如何在不使用设计模式的情况下创建类的实例?
别想太多,这个问题不是坑,就是我们写代码的时候,直接用new关键字直接创建实例。例如在Java语言中,直接通过new关键字调用类的构造函数来完成实例的创建。
类人{}
人 p1 = 新人();
复制代码
而像 Go 这样的语言,虽然是非面向对象的语言,但也提供了一种新的方法来创建指向类型实例的指针。
输入人员结构{}
其中 p1 人
p1 = 新(人)
复制代码
既然可以直接new,肯定有人会问“为什么还要用设计模式?而且听说工厂模式有好几家工厂。”这个问题不难回答,因为我们总是将一些已经定型的业务处理逻辑抽象到各个项目的公共类库中,这样才能找到起点,形成产品开发的闭环……。
以上俚语,大家千万不要学,在公司里很容易有不好的成绩……我来只是为了增强快乐感,我会认真的再说一遍。
刚才提到了一些执行流程清晰但流程细节不同的业务处理逻辑被抽象成一个公共类库。这时,一组行为相同的类会实现一个固定的进程接口,但我们不知道应该在程序中预先创建哪个类实例。只能根据运行参数动态确定。除了类实例化的过程,我们可能还需要收敛一点,以确保产生的实例符合我们的预期。
这样一来,你就会聪明地想到,这个时候,我会让类库暴露一个方法,比如NewXXX。这个NewXXX方法可以产生一个特定类型的实例,并根据条件返回给业务程序。
如果你能想到这一点,那么恭喜你,此时你已经使用了工厂模式。
有时候就是这样,当你写代码的时候,你会在不知不觉中使用设计模式,即使你还不知道。当我们熟悉了设计模式,自然就能有意识地写出更好的代码。
在设计模式中,工厂模式可以细化为三种类型的工厂:
- 简单工厂
- 工厂方法
- 抽象工厂
这三种工厂模式的抽象程度依次增加。就像上面给出的例子,你不自觉地使用了“简单工厂”的设计模式。随着这类流程的类库越来越复杂,需要的抽象越来越高,我们也可以使用后两种工厂模式。这里我将通过一些例子来分析这三种工厂模式。
简单工厂
Go 语言没有构造函数,所以一般会定义 NewXXX 函数来初始化相关的类。当 NewXXX 函数返回一个接口时,它是一个简单的工厂模式。
考虑一个简单的应用场景,其中提供了多种语言的打印机,所有这些都源自一个打印机接口。
// 打印机简单工厂返回的接口类型
类型打印机接口{
打印(名称字符串)字符串
}
复制代码
该程序通过简单工厂为客户提供所需语言的打印机。
func NewPrinter(语言字符串)打印机{
切换语言{
案例“cn”:
返回新的(CnPrinter)
案例“zh”:
返回新的(EnPrinter)
默认:
返回新的(CnPrinter)
}
}
复制代码
在这个场景中,我们首先提供两种语言的打印机,这两种语言都是Printer接口的具体实现类型。
// CnPrinter是Printer接口的实现,它会说中文
键入 CnPrinter 结构 {}
func (*CnPrinter) Print(name string) string {
return fmt.Sprintf("Hello, %s", name)
}
// EnPrinter是Printer接口的实现,它会说中文
输入 EnPrinter 结构 {}
func (*EnPrinter) Print(name string) string {
return fmt.Sprintf("Hello, %s", name)
}
复制代码
在这种场景下,工厂、产品接口和具体产品类之间的关系可以用下图来表示。
客户只需要告诉工厂它想要哪种语言的打印机产品,工厂就会将产品退回给客户。
《示例源码运行Demo,发送go-factory到公众号“网管八宝”获取》
打印机:= NewPrinter(“一个”)
fmt.Println(printer.Print("Bob"))
复制代码
简单工厂模式主要包括3个角色。
- 简单工厂:它是简单工厂模式的核心,负责实现创建所有实例的内部逻辑。工厂类的创建产品类的方法,可以直接被外界调用来创建需要的产品对象。
- 抽象产品:是一个简单工厂创建的所有对象的抽象父类/接口,负责描述所有实例的行为。
- 具体产品:是简单工厂模式的创建对象。
简易工厂的优点是简单,但缺点是如果扩大特定产品的生产,就需要对工厂内部进行改造,增加机箱。一旦产品太多,简单的工厂就会过于臃肿。为了解决这个问题,有一个下一级的工厂模式——工厂方法。
工厂方法
工厂方法模式(Factory Method Pattern)又称多态工厂模式,是指定义一个用于创建对象的接口,但实现该接口的工厂类决定了要实例化哪个产品类,工厂方法实例化该类。推迟到子类。
在工厂方法模式中,产品不再由单个工厂类生产,而是由工厂类的子类创建具体产品。因此,在添加产品时,只需添加对应工厂类的子类,即可解决简单工厂生产过多产品时内部代码臃肿(switch...case 分支过多)的问题。
我们通过一个简单的例子来了解工厂方法的设计思想。考虑一个生产计算器的工厂,每种类型的计算器产品都是由一个子工厂生产的。
注意:Go中没有继承,所以这里说的工厂子类其实是一个具体的工厂类,直接实现了工厂接口 .
// OperatorFactory 工厂接口,由具体工厂类实现
类型 OperatorFactory 接口 {
Create() 数学运算符
}
// MathOperator 实际乘积实现的接口--表示数学运算符应该有什么行为
键入 MathOperator 接口 {
SetOperandA(int)
SetOperandB(int)
ComputeResult() 整数
}
复制代码
现在我们假设程序可以产生两种类型的计算器,加法计算器和乘法计算器,即在工厂方法模式中,有两个子类工厂。
//PlusOperatorFactory是PlusOperator加法运算符的工厂类
输入 PlusOperatorFactory 结构{}
func (pf *PlusOperatorFactory) Create() MathOperator {
返回 &Plus 运算符{
BaseOperator: &BaseOperator{},
}
}
// MultiOperatorFactory 是乘积的工厂
类型 MultiOperatorFactory 结构 {}
func (mf *MultiOperatorFactory) Create() MathOperator{
返回 &MultiOperator{
BaseOperator: &BaseOperator{},
}
}
复制代码
这两个子类工厂分别用于生产加法和乘法计算器。
注意:为便于理解,示例非常简单。在实际场景中,当每个子类工厂创建一个产品实例时,你可以在其中放入复杂的逻辑,而不是简单的新建。
// BaseOperator 是所有 Operator 的基类
// 封装公共方法,因为Go不支持继承,具体Operator类
// 它只能被组合来实现类似继承的行为。
类型 BaseOperator 结构 {
操作数 A,操作数 B 整数
}
func (o *BaseOperator) SetOperandA(operand int) {
o.operandA = 操作数
}
func (o *BaseOperator) SetOperandB(operand int) {
o.operandB = 操作数
}
//PlusOperatorFactory是PlusOperator加法运算符的工厂类
输入 PlusOperatorFactory 结构{}
func (pf *PlusOperatorFactory) Create() MathOperator {
返回 &Plus 运算符{
BaseOperator: &BaseOperator{},
}
}
//PlusOperator 实际产品类——加法运算符
输入 PlusOperator 结构 {
*基地运营商
}
//ComputeResult 计算并得到结果
func (p *PlusOperator) ComputeResult() int {
返回 p.operandA + p.operandB
}
// MultiOperatorFactory 是乘积的工厂
类型 MultiOperatorFactory 结构 {}
func (mf *MultiOperatorFactory) Create() MathOperator{
返回 &MultiOperator{
BaseOperator: &BaseOperator{},
}
}
// MultiOperator 实际产品类——乘数
类型多运算符结构{
*基地运营商
}
func (m *MultiOperator) ComputeResult() int {
返回 m.operandA * m.operandB
}
复制代码
在这种场景下,工厂、产品接口和具体产品类之间的关系可以用下图来表示。
测试运行——客户端使用子类工厂创建产品实例。
// 测试运行
// 运行Demo的示例源代码
// 发送go-factory到公众号“网管Naobi Nao”获取
功能主要(){
var factory OperatorFactory
是 mathOp 数学运算符
工厂 = &PlusOperatorFactory{}
数学运算 = factory.Create()
mathOp.SetOperandB(3)
mathOp.SetOperandA(2)
fmt.Printf("加运算结果:%d\n", mathOp.ComputeResult())
工厂= &MultiOperatorFactory{}
数学运算 = factory.Create()
mathOp.SetOperandB(3)
mathOp.SetOperandA(2)
fmt.Printf("多重运算结果:%d\n", mathOp.ComputeResult())
}
复制代码
工厂方法模式的优点
- 增强了灵活性。对于新产品的创建,只需要多写一个对应的工厂类。
- 典型的解耦框架。高层模块只需要知道产品的抽象类,不需要关心其他的实现类,满足得墨忒耳定律、依赖倒置原则和里氏替换原则。
工厂方法模式的缺点
- 类的数量容易过大,增加了复杂度。
- 它增加了理解系统的抽象性和难度。
- 只能生产一种产品,而这个缺点可以通过使用抽象工厂模式来解决。
无论是简单工厂还是工厂方式,都只能生产一种产品。如果工厂需要在生态中创造多个产品,就需要更进一步,使用三级工厂模式——抽象工厂。
抽象工厂
抽象工厂模式:用于创建一系列相关或相互依赖的对象。
为了更清楚地理解工厂方法模式和抽象工厂模式的区别,我们举一个品牌产品生态的例子。
比如智能家居领域的公司很多,现在有华为和小米。除了知名的手机,他们的工厂还生产电视、空调等家电产品。
如果我们有幸成为他们工厂智能管理软件的供应商,并且我们想在软件系统中抽象出工厂,此时我们不能再使用工厂方法设计模式,因为工厂方法只能使用生产一种产品。
我们先来看看这个抽象工厂用类图表示的抽象多品牌-多产品形式。
让我们简单地用代码实现一个抽象工厂。这家工厂可以生产智能电视和空调。当然,产品的能源使用在代码中还是比较简单的,就是输出相应的消息。
目前抽象工厂有两个实际的工厂类,一个是华为的工厂,一个是小米的工厂,用于实际生产自己的产品设备。
// 运行Demo的示例源代码
// 发送go-factory到公众号“网管Naobi Nao”获取
类型 AbstractFactory 接口 {
CreateTelevision() ITevision
CreateAirConditioner() IAirConditioner
}
输入 ITV 接口 {
手表()
}
输入 IAirConditioner 接口 {
设置温度(整数)
}
输入 HuaWeiFactory 结构{}
func (hf *HuaWeiFactory) CreateTelevision() ITelevision {
返回&华为电视{}
}
func (hf *HuaWeiFactory) CreateAirConditioner() IAirConditioner {
返回&华为空调{}
}
输入 HuaWeiTV struct{}
func (ht *HuaWeiTV) Watch() {
fmt.Println("看华为电视")
}
输入 HuaWeiAirConditioner struct{}
func (ha *HuaWeiAirConditioner) SetTemperature(temp int) {
fmt.Printf("华为空调设置温度为 %d ℃\n", temp)
}
输入 MiFactory 结构{}
func (mf *MiFactory) CreateTelevision() ITelevision {
返回&MiTV{}
}
func (mf *MiFactory) CreateAirConditioner() IAirConditioner {
返回&MiAirConditioner{}
}
输入 MiTV 结构{}
func (mt *MiTV) Watch() {
fmt.Println("看华为电视")
}
输入 MiAirConditioner 结构{}
func (ma *MiAirConditioner) SetTemperature(temp int) {
fmt.Printf("小米空调设置温度为 %d ℃\n", temp)
}
功能主要(){
var factory AbstractFactory
是电视ITevision
var air I空调
工厂 = &华为工厂{}
tv = factory.CreateTelevision()
空气 = factory.CreateAirConditioner()
电视.Watch()
空气.SetTemperature (25)
工厂 = &MiFactory{}
tv = factory.CreateTelevision()
空气 = factory.CreateAirConditioner()
电视.Watch()
空气.SetTemperature (26)
}
复制代码
同样,抽象工厂也具有工厂方法将产品的创建和实例化推迟到工厂子类的特性。如果以后增加VIVO产品,我们可以通过创建VIVO工厂子类来扩展它。
对于抽象工厂我们可以总结以下几点:
- 当系统提供的工厂所需的具体产品不是一个简单的对象,而是在不同的产品层次结构中属于不同类型的多个具体产品时,就需要使用抽象工厂模式。
- 抽象工厂模式是所有工厂模式中最抽象、最通用的形式。
- 抽象工厂模式和工厂方法模式最大的区别在于,工厂方法模式是针对一个产品层次结构的,而抽象工厂模式需要面对多个产品层次结构,一个工厂层次结构可以负责多个不同的产品层次结构。产品对象的创建。
- 当工厂层次结构可以创建属于不同产品层次结构的产品系列中的所有对象时,抽象工厂模式比工厂方法模式更简单、更高效。
抽象工厂模式的优点
- 当需要一个产品系列时,抽象工厂确保客户始终只使用同一产品的产品系列。
- 抽象工厂增强了程序的可扩展性。增加一个新的产品家族,只需要实现一个新的具体工厂,不需要修改现有的代码,符合开闭原则。
抽象工厂模式的缺点
- 指定了所有可能创建的产品集。产品族中新产品难以扩展,需要修改抽象工厂的接口。
- 它增加了理解系统的抽象性和难度。
最后
我们通过几个比较简单的例子来和大家一起了解下三种工厂模式各自的场景和优缺点。在实际使用中,当项目需求一开始不是那么明确的时候,建议先使用一个简单的工厂,等待我们业务了解。更彻底之后,如果真的需要升级到工厂方法,现在还为时不晚。
抽象工厂也是如此。如果确定可以引入产品生态的概念进行更好的领域建模,现在开始使用抽象工厂还为时不晚。
如果觉得今天的文章不错,请多多点赞和支持。如果你喜欢+阅读,你将可以在一周内进入下一个设计模式。 .
版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议。转载请附上原文出处链接和本声明。
这篇文章的链接: https://homecpp.art/1413/8159/1724
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明