一文搞懂设计模式之建造者模式

大家好,我是晴天,我们又见面了,这周我们继续学习一文搞懂设计模式系列,本周将一起学习建造者模式(生成器模式)

本文目录

什么是建造者模式

建造者模式(也称为生成器模式)是一种创建型设计模式。

基本概念

建造者模式主要包含以下几个角色:

  1. 产品(Product): 要创建的复杂对象,它由多个部件组成。

  2. 抽象建造者(Builder): 定义了创建产品的抽象接口,包括创建产品的各个部件的方法。

  3. 具体建造者(Concrete Builder): 实现抽象建造者接口,负责实际构建产品的各个部件,通常包含一个用于获取构建完成后产品的方法。

  4. 指导者(Director): 负责使用建造者接口构建产品,通常包含一个构建方法,该方法通过调用建造者的方法按特定顺序构建产品。

  5. 客户端(Client): 使用指导者构建复杂对象的客户端。

基础关系

建造者模式的几个角色之间的关系如下:

  1. 产品(Product): 要创建的复杂对象,它由多个部件组成。产品的内部结构通常由多个部分组成,而具体的部分可以由抽象建造者和具体建造者来定义。

  2. 抽象建造者(Builder): 定义了创建产品的抽象接口,包括创建产品的各个部件的方法。抽象建造者中的方法通常对应产品的各个部分,但是这些方法通常是空的或者有默认实现。

  3. 具体建造者(Concrete Builder): 实现抽象建造者接口,负责实际构建产品的各个部件。具体建造者包含了具体的构建逻辑,它知道如何组装产品的各个部分,并负责最终返回构建好的产品。

  4. 指导者(Director): 负责使用建造者接口构建产品。指导者通常包含一个构建方法,该方法通过调用建造者的方法按照一定的顺序来构建产品。指导者知道构建的步骤和顺序,但它并不知道具体的产品是如何构建的。

  5. 客户端(Client): 客户端通过指导者来构建产品,而不需要知道产品的具体构建过程。客户端创建一个指导者,并将具体的建造者传递给指导者。然后,客户端通过调用指导者的构建方法来获取构建好的产品。

这些角色协同工作,使得构建过程与最终产品的表示相分离。客户端通过指导者来构建产品,而具体的构建过程由具体建造者完成。这种分离使得可以更灵活地改变产品的内部表示,同时客户端不必关心具体的构建细节。

现实举例

一个实际生活中的例子是制造汽车。汽车通常由多个部分组成,例如引擎、底盘、车身、轮胎等。而不同类型的汽车,如轿车、卡车、越野车等,可能具有不同的配置和特征。

例子类图

在这种情况下,建造者模式可以用于创建不同类型的汽车,而无需改变构建过程的逻辑。让我们看看这个例子的几个角色:

  1. 产品(Product): 汽车是要创建的产品,它由引擎、底盘、车身、轮胎等多个部分组成。

  2. 抽象建造者(Builder): 定义了创建汽车的抽象接口,包括创建汽车的各个部分的方法。例如,有一个抽象方法用于构建引擎、构建底盘、构建车身等。

  3. 具体建造者(Concrete Builder): 实现了抽象建造者接口,负责实际构建汽车的各个部分。比如,轿车建造者负责构建小型引擎和轻巧的车身,而卡车建造者负责构建大型引擎和坚固的底盘。

  4. 指导者(Director): 负责使用建造者接口构建汽车。指导者知道构建汽车的步骤和顺序,但并不知道具体的汽车是如何构建的。它可以接受不同类型的具体建造者,并使用它们来构建不同配置的汽车。

  5. 客户端(Client): 客户端通过创建一个指导者,并将具体建造者传递给指导者,来构建汽车。客户端只需要调用指导者的构建方法,而不需要了解汽车的具体构建过程。

通过这种方式,可以轻松地创建不同类型的汽车,而不必改变指导者的代码。每个具体建造者负责自己类型汽车的构建细节,从而实现了构建过程和产品的解耦。这种模式使得在需要新类型的汽车时,可以添加新的具体建造者,而无需修改现有的构建逻辑。

代码实现

我们以上述实例,来实现一个简单的demo

package mainimport "fmt"// 产品:汽车
type Car struct {Engine  stringChassis stringBody    stringTires   string
}// 抽象建造者接口
type Builder interface {BuildEngine()BuildChassis()BuildBody()BuildTires()GetCar() *Car
}// 具体建造者:轿车
type SedanBuilder struct {car *Car
}func NewSedanBuilder() *SedanBuilder {return &SedanBuilder{car: &Car{}}
}func (sb *SedanBuilder) BuildEngine() {sb.car.Engine = "Small Engine"
}func (sb *SedanBuilder) BuildChassis() {sb.car.Chassis = "Light Chassis"
}func (sb *SedanBuilder) BuildBody() {sb.car.Body = "Sleek Body"
}func (sb *SedanBuilder) BuildTires() {sb.car.Tires = "Standard Tires"
}func (sb *SedanBuilder) GetCar() *Car {return sb.car
}// 具体建造者:卡车
type TruckBuilder struct {car *Car
}func NewTruckBuilder() *TruckBuilder {return &TruckBuilder{car: &Car{}}
}func (tb *TruckBuilder) BuildEngine() {tb.car.Engine = "Large Engine"
}func (tb *TruckBuilder) BuildChassis() {tb.car.Chassis = "Heavy Chassis"
}func (tb *TruckBuilder) BuildBody() {tb.car.Body = "Robust Body"
}func (tb *TruckBuilder) BuildTires() {tb.car.Tires = "Off-road Tires"
}func (tb *TruckBuilder) GetCar() *Car {return tb.car
}// 指导者
type Director struct {builder Builder
}func NewDirector(builder Builder) *Director {return &Director{builder: builder}
}func (d *Director) Construct() *Car {d.builder.BuildEngine()d.builder.BuildChassis()d.builder.BuildBody()d.builder.BuildTires()return d.builder.GetCar()
}func main() {// 构建轿车sedanBuilder := NewSedanBuilder()director := NewDirector(sedanBuilder)sedanCar := director.Construct()fmt.Println("Sedan Car:", sedanCar)// 构建卡车truckBuilder := NewTruckBuilder()director = NewDirector(truckBuilder)truckCar := director.Construct()fmt.Println("Truck Car:", truckCar)
}

代码解释

以下是一个简化的使用 Go 语言实现建造者模式的例子,以汽车制造为例:

package mainimport "fmt"// 产品:汽车
type Car struct {Engine  stringChassis stringBody    stringTires   string
}// 抽象建造者接口
type Builder interface {BuildEngine()BuildChassis()BuildBody()BuildTires()GetCar() *Car
}// 具体建造者:轿车
type SedanBuilder struct {car *Car
}func NewSedanBuilder() *SedanBuilder {return &SedanBuilder{car: &Car{}}
}func (sb *SedanBuilder) BuildEngine() {sb.car.Engine = "Small Engine"
}func (sb *SedanBuilder) BuildChassis() {sb.car.Chassis = "Light Chassis"
}func (sb *SedanBuilder) BuildBody() {sb.car.Body = "Sleek Body"
}func (sb *SedanBuilder) BuildTires() {sb.car.Tires = "Standard Tires"
}func (sb *SedanBuilder) GetCar() *Car {return sb.car
}// 具体建造者:卡车
type TruckBuilder struct {car *Car
}func NewTruckBuilder() *TruckBuilder {return &TruckBuilder{car: &Car{}}
}func (tb *TruckBuilder) BuildEngine() {tb.car.Engine = "Large Engine"
}func (tb *TruckBuilder) BuildChassis() {tb.car.Chassis = "Heavy Chassis"
}func (tb *TruckBuilder) BuildBody() {tb.car.Body = "Robust Body"
}func (tb *TruckBuilder) BuildTires() {tb.car.Tires = "Off-road Tires"
}func (tb *TruckBuilder) GetCar() *Car {return tb.car
}// 指导者
type Director struct {builder Builder
}func NewDirector(builder Builder) *Director {return &Director{builder: builder}
}func (d *Director) Construct() *Car {d.builder.BuildEngine()d.builder.BuildChassis()d.builder.BuildBody()d.builder.BuildTires()return d.builder.GetCar()
}func main() {// 构建轿车sedanBuilder := NewSedanBuilder()director := NewDirector(sedanBuilder)sedanCar := director.Construct()fmt.Println("Sedan Car:", sedanCar)// 构建卡车truckBuilder := NewTruckBuilder()director = NewDirector(truckBuilder)truckCar := director.Construct()fmt.Println("Truck Car:", truckCar)
}

在这个例子中,Car 表示产品,Builder 是抽象建造者接口,SedanBuilderTruckBuilder 是具体建造者,Director 是指导者。客户端可以通过创建不同类型的具体建造者来构建不同类型的汽车。这样,可以轻松地扩展和修改汽车的构建过程,而不必改变客户端的代码。

代码优化

上述代码,无论是轿车的建造者还是卡车的建造者,都使用了相同的 Car 结构,并且直接为这个结构中的字段赋值。这可能在一些实际情况下并不是最灵活的做法,尤其是在不同类型的汽车具有不同属性时。

一个更灵活的方式是在 Car 结构中引入一个更通用的字段,例如一个 Options 字段,它可以是一个映射或其他适当的数据结构,以便每个具体建造者可以根据需要添加不同的属性。以下是相应的修改:

package mainimport ("fmt"
)// 产品:汽车
type Car struct {Options map[string]string
}// 抽象建造者接口
type Builder interface {BuildEngine()BuildChassis()BuildBody()BuildTires()GetCar() *Car
}// 具体建造者:轿车
type SedanBuilder struct {car *Car
}func NewSedanBuilder() *SedanBuilder {return &SedanBuilder{car: &Car{Options: make(map[string]string)}}
}func (sb *SedanBuilder) BuildEngine() {sb.car.Options["Engine"] = "Small Engine"
}func (sb *SedanBuilder) BuildChassis() {sb.car.Options["Chassis"] = "Light Chassis"
}func (sb *SedanBuilder) BuildBody() {sb.car.Options["Body"] = "Sleek Body"
}func (sb *SedanBuilder) BuildTires() {sb.car.Options["Tires"] = "Standard Tires"
}func (sb *SedanBuilder) GetCar() *Car {return sb.car
}// 具体建造者:卡车
type TruckBuilder struct {car *Car
}func NewTruckBuilder() *TruckBuilder {return &TruckBuilder{car: &Car{Options: make(map[string]string)}}
}func (tb *TruckBuilder) BuildEngine() {tb.car.Options["Engine"] = "Large Engine"
}func (tb *TruckBuilder) BuildChassis() {tb.car.Options["Chassis"] = "Heavy Chassis"
}func (tb *TruckBuilder) BuildBody() {tb.car.Options["Body"] = "Robust Body"
}func (tb *TruckBuilder) BuildTires() {tb.car.Options["Tires"] = "Off-road Tires"
}func (tb *TruckBuilder) GetCar() *Car {return tb.car
}// 指导者
type Director struct {builder Builder
}func NewDirector(builder Builder) *Director {return &Director{builder: builder}
}func (d *Director) Construct() *Car {d.builder.BuildEngine()d.builder.BuildChassis()d.builder.BuildBody()d.builder.BuildTires()return d.builder.GetCar()
}func main() {// 构建轿车sedanBuilder := NewSedanBuilder()director := NewDirector(sedanBuilder)sedanCar := director.Construct()fmt.Println("Sedan Car:", sedanCar)// 构建卡车truckBuilder := NewTruckBuilder()director = NewDirector(truckBuilder)truckCar := director.Construct()fmt.Println("Truck Car:", truckCar)
}

适用场景

建造者模式适用于以下场景:

  1. 创建复杂对象: 当需要创建的对象具有复杂的内部结构,由多个部分组成,并且这些部分之间的组装方式有一定的规律时,可以使用建造者模式。这有助于将复杂对象的构建过程与其表示分离,使得可以更灵活地构建不同的表示。

  2. 避免构造方法的参数列表过长: 当一个类的构造方法需要传递很多参数,而且这些参数之间存在一定的关联关系时,使用建造者模式可以避免构造方法参数列表过长的问题。通过将这些参数封装到一个具体的建造者中,可以更清晰地管理和维护。

  3. 支持不同的构建顺序: 如果需要支持不同的构建顺序来构建对象,可以使用建造者模式。具体建造者可以实现不同的构建步骤,指导者根据需要调用这些步骤,以实现不同的构建顺序。

  4. 构建过程具有复杂的条件逻辑: 当构建对象的过程涉及复杂的条件逻辑,不同的条件可能导致不同的构建步骤时,建造者模式可以提供一种清晰的方式来处理这种复杂性。

建造者模式的优缺点

优点:

  1. 分离构建过程和表示: 建造者模式将一个复杂对象的构建过程与其最终表示相分离。这使得可以更容易地改变产品的内部表示,同时客户端不必关心具体的构建过程。

  2. 更好的控制构建过程: 通过使用指导者来控制构建过程,可以灵活地组合各个部分,实现不同的构建顺序和构建逻辑。这使得可以更好地控制对象的创建过程。

  3. 更容易扩展新的具体建造者: 向系统中添加新的具体建造者相对容易,不需要修改指导者的代码。这符合开闭原则,使得系统更容易扩展。

  4. 更易于改变产品的内部表示: 由于构建过程与最终产品的表示分离,可以更灵活地改变产品的内部结构,而不会对客户端产生影响。

缺点:

  1. 增加了系统的复杂性: 引入了多个角色(指导者、抽象建造者、具体建造者)和接口,从而增加了系统的复杂性。对于简单的对象,建造者模式可能显得过于繁琐。

  2. 可能会产生多余的Builder对象: 如果产品的构建过程比较简单,那么可能会存在一些不必要的具体建造者实现,增加了系统的开销。

  3. 不容易发现构建过程中的错误: 在编译时很难发现具体建造者中构建过程的错误,因为具体建造者的方法通常是在运行时调用的。这可能导致一些在构建过程中的逻辑错误只能在实际运行时被发现。

写在最后

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

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【MySQL】:事务(下)

事务 一.MVCC机制(读-写)1.预备知识1.三个记录隐藏字段2.undo日志 2.模拟MVCC3.Read View4.整体流程 二.RC与RR的本质区别1.当前读和快照读在RR级别下的区别1.测试一2.测试二 2.RR 与 RC的本质区别 数据库并发的场景有三种: 读-读 :不存在任何问题&…

【六大排序详解】中篇 :选择排序 与 堆排序

选择排序 与 堆排序 选择排序 选择排序 与 堆排序1 选择排序1.1 选择排序原理1.2 排序步骤1.3 代码实现 2 堆排序2.1 堆排序原理2.1.1 大堆与小堆2.1.2 向上调整算法2.1.3 向下调整算法 2.2 排序步骤2.3 代码实现 3 时间复杂度分析 Thanks♪(・ω・)&#…

Vue在页面上添加水印

第一步:在自己的项目里创建一个js文件;如图所示我在在watermark文件中创建了一个名为waterMark.js文件。 waterMark.js /** 水印添加方法 */ let setWatermark (str1, str2) > {let id 1.23452384164.123412415if (document.getElementById(id) …

WPF中数据绑定转换器Converter

使用场景:ViewModel中的数据如果跟View中的数据类型不匹配。 下面是以int类型调控是否可见为例子 步骤一:创建转换器类 在xaml中查看Converter的定义可以知道Converter是一个接口类型,因此转换器的类定义需要使用这个接口 internal class Vi…

计算机视觉技术-使用图像增广进行训练

让我们使用图像增广来训练模型。 这里,我们使用CIFAR-10数据集,而不是我们之前使用的Fashion-MNIST数据集。 这是因为Fashion-MNIST数据集中对象的位置和大小已被规范化,而CIFAR-10数据集中对象的颜色和大小差异更明显。 CIFAR-10数据集中的前…

如何使用Docker部署Dashy并无公网ip远程访问管理界面

文章目录 简介1. 安装Dashy2. 安装cpolar3.配置公网访问地址4. 固定域名访问 简介 Dashy 是一个开源的自托管的导航页配置服务,具有易于使用的可视化编辑器、状态检查、小工具和主题等功能。你可以将自己常用的一些网站聚合起来放在一起,形成自己的导航…

不同参数规模大语言模型在不同微调方法下所需要的显存总结

原文来自DataLearnerAI官方网站: 不同参数规模大语言模型在不同微调方法下所需要的显存总结 | 数据学习者官方网站(Datalearner)https://www.datalearner.com/blog/1051703254378255 大模型的微调是当前很多人都在做的事情。微调可以让大语言模型适应特定领域的任…

WordPress主题大前端DUX v8.3源码下载

DUX主题8.3版本更新内容: 新增:Cloudflare Turnstile 免费验证功能 新增:子菜单页面模版,支持多级页面 新增:手机端文章内表格自动出现横向滚动条,可集体或单独设置滚动宽度 新增:标签云页面模版…

springboot 共享自习室座位管理系统 -计算机毕业设计源码55732

摘 要 随着互联网趋势的到来,各行各业都在考虑利用互联网将自己推广出去,最好方式就是建立自己的互联网系统,并对其进行维护和管理。在现实运用中,应用软件的工作规则和开发步骤,采用Java技术建设共享自习室座位管理系…

零基础入门网络安全必看的5本书籍(附PDF)

书中自有黄金屋,书中自有颜如玉。很多人学习一门技术都会看大量的书籍,经常也有朋友询问:零基础刚入门,应该看哪些书?应该怎么学?等等问题。今天就整理了5本零基础入门网络安全必看书籍,希望能帮…

overleaf 支持中文

基本操作 左上角menu中,切换compiler 到xelatex。 然后在\documentclass声明下面加一个 \usepackage{ctex}。 使用\usepackage{xecjk}可能也可以,但会有警告。 警告分析 Font “FandolSong-Regular” does not contain requested Script “CJK”。 网上…

关于标准那些事——第五篇 两仪

国家标准的编写,对于标准的名称和结构,很多人往往是不那么在意的,但这恰恰也是非常重要的点,今天就给大家分享一下这太极所生的“两仪”。我会用最精简的文字概括出核心内容,让大家有一个初步且完整的概念,…