【设计模式】go语言中的 [函数选项,单例,工厂,责任链] 常用的设计模式
文章目录
- 前言
- 一、函数选项模式
- 二、单例模式
- 三、工厂模式
- 四、责任链模式
前言
宿舍每人 温度38℃+ 大寄
设计模式很重要,设计模式其实就是为了解决某一类问题而形成的代码写法,设计模式很多,但是并不是每个都很常用,我们只讲解─些常用的
设计模式分类大家可以参考: https://juejin.cn/post/6908528350986240014
go中最常用的设计模式是函数选项模式, grpc,kratos等等开源项目中比比皆是
有时候一个函数会有很多参数,为了方便函数的使用,我们会给希望给一些参数设定默认值,调用时只需要传与默认值不同的参数即可,类似于python里面的默认参数和字典参数,在java中可以提供多种构造函数,虽然 golang里面既没有默认参数也没有字典参数,但是我们有选项模式
注:函数选项模式是用来构造对象
go中没有构造器,要构造一个对象一般是直接实例化,或者采用NewXX的模式,其中
- 实例化然后复制属性的模式会写很多行代码,虽然可以直接采用A{name:xxx, b:xxx}的模式,但
是如果有个c属性的默认值不是空字符串咋办? - 所以整个过程使用实例化加属性设置的方式会让实例化很麻烦
- 使用new的方式也会有问题:如果可以一个参数设置,如果可以两个参数设置不得不使用
newA,newAandB等等各种组合设置 - 有没有办法可以同时解决上面的问题呢?-函数选项模式
选项模式的应用
从这里可以看到,为了实现选项的功能,我们增加了很多的代码,实现成本相对还是较高的,所以实践中需要根据自己的业务场景去权衡是否需要使用。个人总结满足下面条件可以考虑使用选项模式
- 参数确实比较复杂,影响调用方使用
- 参数确实有比较清晰明确的默认值
- 为参数的后续拓展考虑
在golang 的很多开源项目里面也用到了选项模式,比如 grpc中的 rpc方法就是采用选项模式设计的,除了必填的rpc参数外,还可以一些选项参数,grpc_retry就是通过这个机制实现的,可以实现自动重试功能。
一、函数选项模式
函数选项模式(Functional Options Pattern):
函数选项模式是一种用于函数设计的模式,它允许函数接受可选参数,这些参数可以通过一种简单、灵活的方式进行设置,从而避免出现过多的函数重载。在Go语言中,函数选项模式通常使用可变参数列表结合函数类型参数的方式实现。通过该模式,我们可以更加灵活地控制函数的行为,并且可以方便地扩展函数的功能。
在这个示例代码中,DbOptions
结构体类型定义了用于保存数据库连接选项的字段。为了实现函数选项模式,我们定义了一个名为Option
的函数类型,用于设置DbOptions
结构体中的字段值。WithHost
函数是一个实现了Option
函数类型的具体函数,用于设置数据库连接的主机地址。NewOpts
函数接受任意数量的Option
函数类型的参数,并将其应用于一个DbOptions
结构体类型的实例上。
在NewOpts
函数中,我们首先定义一个带有默认值的DbOptions
结构体类型的实例,并将其保存在dbopts
变量中。接着,我们遍历所有传入的Option
函数类型参数,逐个将其应用于dbopts
变量中的字段。最后,我们返回dbopts
变量的值,这是一个包含了所有设置过的选项的DbOptions
结构体类型实例。
在main
函数中,我们调用NewOpts
函数获取默认选项,并将其打印输出。此时,输出的结果中将包含我们在WithHost
函数中设置的主机地址选项。这种方式可以使得我们在使用该函数时,只需要传递需要设置的选项,而不需要关心默认选项或者选项的顺序。
package mainimport "fmt"type DbOptions struct {Host stringPort intUsername stringPassword stringDBName string
}type Option func(*DbOptions)// 这个函数主要用来设置Host
func WithHost(host string) Option {return func(o *DbOptions) {o.Host = host}
}func NewOpts(opts ...Option) DbOptions {//先实例化号dbOptions,填充上默认值dbopts := &DbOptions{Host: "127.0.0.1",Port: 3306,Username: "root",Password: "123456",DBName: "test",}for _, option := range opts {option(dbopts)}return *dbopts
}func main() {//NewDBClient(WithHost("192.168.0.1"))//opts := NewOpts(WithHost("192.168.0.1"))opts := NewOpts()fmt.Println(opts)//函数选项牧师大量引用了函数,
}
二、单例模式
单例模式(Singleton Pattern):
单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供了一个全局访问点来访问该实例。在Go语言中,单例模式通常使用包级别变量或者全局变量来实现,因为包级别变量只会被初始化一次,而全局变量则是唯一的。
1. 使用sync.Once实现单例模式
在下面的代码中,使用了sync.Once
类型来确保在程序运行时只执行一次初始化逻辑,以创建唯一的DBPool
实例。具体实现步骤如下:
a. 在GetDBPool2
函数中,调用sync.Once
的Do
方法,并将初始化逻辑封装在一个匿名函数中。
b. 在匿名函数中创建DBPool
实例,并将其赋值给dbPoolIns
变量。
c. 返回dbPoolIns
变量。
使用sync.Once
实现的单例模式具有并发安全性,并且无需加锁即可实现懒加载,但需要创建匿名函数,代码稍微有些复杂。
2. 使用sync.Mutex和atomic实现单例模式
在下面的代码中,使用了sync.Mutex
和atomic
两个包来实现单例模式。具体实现步骤如下:
a. 在GetDBPool
函数中,首先判断initialized
变量是否为1,如果是,则直接返回dbPoolIns
变量。
b. 如果initialized
变量不是1,则获取锁,防止其他goroutine
同时执行初始化逻辑。
c. 在获取锁后,再次判断initialized
变量是否为0,如果是,则创建DBPool
实例,并将其赋值给dbPoolIns
变量,然后使用atomic.StoreUint32
函数将initialized
变量设置为1。
d. 释放锁并返回dbPoolIns
变量。
使用sync.Mutex
和atomic
实现的单例模式也具有并发安全性,并且代码比较简单易懂,但需要显式加锁,并且无法实现懒加载。
package mainimport ("sync""sync/atomic"
)type DBPool struct {Host stringPort intUserName string
}var dbPoolIns *DBPool
var lock sync.Mutex
var initialized uint32// 有问题的方法,并发
// 加锁 - 功能上没有问题,但是性能不好
// 高并发下,有bug
// goroutine1 进来,实例化dbPoolIns = &DBPool{}进想到一半,goroutine2进来,读到dbPoolIns != nil,返回dbPoolIns
func GetDBPool() *DBPool {if atomic.LoadUint32(&initialized) == 1 {return dbPoolIns}lock.Lock()defer lock.Unlock()if initialized == 0 {dbPoolIns = &DBPool{}//原子操作 一旦有一个线程执行到了atomic.LoadUint32(&initialized) initialized就会被保护 确保initialized的原子性atomic.StoreUint32(&initialized, 1)}return dbPoolIns
}var once sync.Oncefunc GetDBPool2() *DBPool {once.Do(func() {dbPoolIns = &DBPool{}})return dbPoolIns
}
func main() {}
三、工厂模式
工厂模式(Factory Pattern):
工厂模式是一种创建型设计模式,它提供了一个抽象工厂接口来创建一系列相关的对象,而无需指定具体的类。在Go语言中,工厂模式通常使用接口和结构体的组合来实现,从而实现对不同对象的创建。通过该模式,我们可以更加灵活地创建对象,并且可以方便地扩展对象的种类。
在这个示例中,我们定义了一个名为“Book”的接口,该接口有一个名为“Name”的方法。然后,我们定义了三个具体的书籍类型:ChineseBook、MathBook和EnglishBook,并让它们都实现了“Book”接口的“Name”方法。
接下来,我们定义了一个名为“GetBook”的函数,该函数接受一个字符串参数“name”,并根据名称返回一个具体的书籍类型。我们使用一个简单的 switch 语句来实现这个功能,并在默认情况下返回 nil。
最后,在“main”函数中,我们调用“GetBook”函数,传递不同的书籍名称,并打印出返回的书籍类型。
工厂模式的好处是,在增加新的书籍类型时,我们只需要添加新的具体类型和一个对应的 case 语句,而不需要修改现有的代码。这使得我们的代码更加灵活和可扩展。
在实际应用中,工厂模式可以帮助我们更好地组织和管理代码,并将复杂的对象创建过程封装起来,使得我们的代码更加易于维护和扩展。
package mainimport "fmt"/*
在小明的学校,每一年开学都会发教材,
主要包括语文书、数学书、英语书,还有各种练习试卷。
这一天,小明去领了三本教材,分别是语文书、数学书和英语书,老师忙不过来,指定某个同学去发书,
同学们都去这个同学这里去领书。这个同学就是工厂。
*/
type Book interface {Name() string
}
type chineseBook struct {name string
}func (cb *chineseBook) Name() string {return cb.name
}type mathBook struct {name string
}func (mb *mathBook) Name() string {return mb.name
}type englishBook struct {name string
}func (eb *englishBook) Name() string {return eb.name
}
func GetBook(name string) Book {switch name {case "语文书":return &chineseBook{name: name}case "数学书":return &mathBook{name: name}case "英语书":return &englishBook{name: name}default:return nil}
}
func main() {fmt.Println(GetBook("语文书"))fmt.Println(GetBook("数学书"))
}
四、责任链模式
责任链模式(Chain of Responsibility Pattern):
责任链模式是一种行为型设计模式,它将一系列对象连接在一起,形成一个责任链。当请求被发送到该链上时,每个对象都有机会处理请求或将其传递给下一个对象,直到请求被处理为止。在Go语言中,责任链模式通常使用链表或者数组来实现,从而实现对请求的处理。通过该模式,我们可以更加灵活地处理请求,并且可以方便地扩展处理的对象。
在这个示例中,我们可以将 Assigner 接口看做一个处理器对象,用来处理请求并返回相应的书籍或文献。在 assigner 结构体中,我们实现了 GetBook 和 GetPaper 方法来获取不同类型的书籍或文献。在 chineseBookAssigner 结构体中,我们仅仅处理了语文书籍的请求,而其他类型的请求则会被忽略。这样,如果一个请求需要被处理,它就会被传递给第一个处理器对象,然后沿着处理链一直传递到最后一个处理器对象,直到找到能够处理请求的处理器对象或者到达处理链的末端。
在 main 函数中,我们创建了一个 chineseBookAssigner 对象并调用其 GetBook 方法来获取语文书籍的对象。由于 chineseBookAssigner 只处理语文书籍的请求,所以我们可以看到它成功地返回了一个 chineseBook 对象。而对于其他类型的请求,则会返回 nil。这样,我们就实现了一个简单的责任链模式。
package mainimport "fmt"/*
在小明的学校,每一年开学都会发教材,
主要包括语文书、数学书、英语书,还有各种练习试卷。
这一天,小明去领了三本教材,分别是语文书、数学书和英语书,老师忙不过来,指定某个同学去发书,
同学们都去这个同学这里去领书。这个同学就是工厂。
*/
type Book interface {Name() string
}
type Paper interface {Name() string
}type chineseBook struct {name string
}
type chinesePaper struct {name string
}func (cb *chineseBook) Name() string {return cb.name
}type mathBook struct {name string
}func (mb *mathBook) Name() string {return mb.name
}type englishBook struct {name string
}func (eb *englishBook) Name() string {return eb.name
}type Person struct{}type Assigner interface {GetBook(name string) BookGetpaper(string) Paper
}
type assigner struct{}func (a *assigner) GetBook(name string) Book {switch name {case "语文书":return &chineseBook{name: name}case "数学书":return &mathBook{name: name}case "英语书":return &englishBook{name: name}default:return nil}
}type chineseBookAssigner struct {
}func (cba *chineseBookAssigner) GetBook(name string) Book {if name == "语文书" {return &chineseBook{name: name}}return nil
}
func main() {var a chineseBookAssignerfmt.Println(a.GetBook("语文书"))fmt.Println(a.GetBook("数学书"))
}
相关文章:
【设计模式】go语言中的 [函数选项,单例,工厂,责任链] 常用的设计模式
文章目录前言一、函数选项模式二、单例模式三、工厂模式四、责任链模式前言 宿舍每人 温度38℃+ 大寄 设计模式很重要,设计模式其实就是为了解决某一类问题而形成的代码写法,设计模式很多,但是并不是每个都很常用,我们…...
2017系统分析师案例分析真题背记内容
前言 以下内容仅为个人根据当年系分案例真题问题整理的偏需要记背的考点答案,方便个人背诵和记忆使用。方便文字转语音,所以内容全为纯文字内容,以下内容仅供参考。 背记内容 微服务 微服务中应该包含的内容有:资源、对资源的…...
C++和C的区别
答:从宏观角度和微观角度分析微观角度:函数原型有区别,在c中,函数原型有参数和没有参数是不同的,并且允许申明多个同名的函数,只要他们的参数列表不同或者返回值不同即可,但是在c语言中不能。C引…...

【React教程】一、React简介
一、React简介 React是一个用于构建用户界面的JavaScript库,它是Facebook的内部项目,用来架设Instagram的网站,并于2013年5月开源。React主要用于构建Ul,很多人认为React 是 MVC 中的 V(视图)。由于拥有较高的性能&…...

运动蓝牙耳机什么牌子好,比较好的运动蓝牙耳机推荐
现在市面上的运动蓝牙耳机越来越多,在选择耳机的时候应该如何入手呢?最重要的是需要按照自己的需求来选择,但在耳机的配置上不能忽视的是耳机的防水等级,运动耳机对防水等级的要求更高,这样能够更好地防御汗水浸湿耳机…...
[深入理解SSD系列 闪存实战2.1] NAND FLASH特性串烧 | 不了解闪存特性,你能用好闪存产品吗?
前言 为了利用好闪存, 发挥闪存的优势, 以达到更好的性能和使用寿命, 那自然要求了解闪存特性。 闪存作为一种相对较新的存储介质, 有很多特别的特性。 一.闪存的特性 凡是采用Flash Memory的存储设备,可以统称为闪存存储。我们经常谈的固态硬盘(SSD),可以由volatile/…...

DJI ROS dji_sdk 源码分析|整体框架
DJI ROS dji_sdk 源码分析|整体框架launch文件CMakeLists.txtcpp文件main.cppOSDK 是一个用于开发无人机应用程序的开发工具包,基于OSDK 开发的应用程序能够运行在机载计算机上(如Manifold 2),开发者通过调用OSDK 中指定的接口能够…...

HT32合泰单片机开发环境搭建和配置教程
HT32合泰(Holtek)单片机开发环境搭建安装教程 前言 最近在准备合泰杯的比赛,在看合泰官方的PPT和数据手册学习,顺便做个合泰单片机的开发环境搭建教程。 合泰杯比赛发放的开发板是ESK32-30501,用的单片机是HT32F52352。 合泰杯官网地址&a…...

动态内存分配之伙伴算法
伙伴算法 伙伴算法是一种在计算机内存管理中使用的算法,用于分配和释放内存。它是一种基于二叉树的动态内存分配算法,可以高效地分配和合并内存块。伙伴算法是一种按照固定大小分配内存的算法,例如,每个内存块的大小为2的n次幂&a…...

CGAL 根据扫描线方向和角度对法向量进行重定向
目录一、算法原理1、主要函数二、代码实现一、算法原理 最小生成树对法向量定向的结果在具有许多尖锐特征和遮挡的机载点云数据中结果并不理想。scanline_orient_normals()是专门用于具有扫描线特性的点云法向量重定向的替代方法。它充分利用了某些激光雷达扫描器的LAS特性&…...

一个C#开发的开源的快速启动工具
更多开源项目请查看:一个专注推荐.Net开源项目的榜单 平常计算机安装软件比较多、或者工作涉及的文件比较多,很多人都会直接放在桌面,一方面不安全,还不容易查找,这时候我们往往,都会放在其他硬盘内&#x…...

Paddle项目调试记录
PaddlePaddle是百度公司提出的深度学习框架。近年来深度学习在很多机器学习领域都有着非常出色的表现,在图像识别、语音识别、自然语言处理、机器人、网络广告投放、医学自动诊断和金融等领域有着广泛应用。面对繁多的应用场景,深度学习框架有助于建模者…...
3月11日,30秒知全网,精选7个热点
///微盟集团宣布接入百度文心一言 据介绍,微盟SaaS产品和数字营销服务将与文心一言的技术能力实现深度融合,通过AIGC技术,深化微盟在营销AI创意内容生产、智能营销、智能客服、智能经营等方面的布局 ///T3出行与华为云深化业务合作 双方将在…...
C win32基础学习(四)
上一篇我们已经介绍了关于窗口处理函数的知识。本篇我们说一下注册窗口类,创建窗口和显示窗口的内容。 前文 窗口创建过程 定义WinMain函数 定义窗口处理函数(自定义,处理消息) 注册窗口类(向操作系统写入一些数据) 创建窗口&…...
Java 日期时间API(Java 8及以上)
Java 8及以上版本提供了新的日期时间API,其中包括了LocalDate、LocalTime、LocalDateTime、ZonedDateTime、Duration、Period等类,这些类提供了更加丰富和灵活的日期时间操作方法。 LocalDate LocalDate类表示一个本地日期,不包含时间和时区…...

DHCP的配置
实验目的熟悉DHCP的应用场景掌握DHCP的配置方法实验拓扑DHCP的配置如图15-2所示: 图15-2:DHCP的配置 实验步骤配置IP地址<Huawei>system-view Enter system view, return user view with Ctrl+Z....

JavaWeb14-线程池
目录 1.传统线程的缺点 2.线程池的定义 3.线程池的优点 4.线程池的创建/使用(2类7种) 4.1.通过Executors(执行器)自动创建(6种) ①Executors.newFixedThreadPool:创建⼀个固定⼤⼩的线程池…...

[qiankun+nuxt]子应用请求本地文件报错404
前言 目前公司的前端架构是qiankunnuxt做的微前端项目 问题说明 在子应用中,前端需要模拟一些数据,方便后期演示调整而不需要重新打包 所以将一些数据存储到了本地的json文件中,但是获取时报了404的错误,找不到该文件。 页面报错…...

【Qt网络编程】实现TCP协议通信
文章目录概要:本期主要讲解QT中对于TCP协议通信的实现。一、TCP协议二、Qt中TCP协议处理1.QTcpSocket2.QTcpServer三、Qt实现TCP通信1.客户端2.服务器端结尾概要:本期主要讲解QT中对于TCP协议通信的实现。 一、TCP协议 传输控制协议(TCP&am…...

Webpack打包———处理样式资源
基本使用 本质上,webpack 是一个用于现代 JavaScript 应用程序的 静态模块打包工具。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图(dependency graph),然后将你项目中所需的每一个模块组合成一个或多个 bundles&a…...

【Python】 -- 趣味代码 - 小恐龙游戏
文章目录 文章目录 00 小恐龙游戏程序设计框架代码结构和功能游戏流程总结01 小恐龙游戏程序设计02 百度网盘地址00 小恐龙游戏程序设计框架 这段代码是一个基于 Pygame 的简易跑酷游戏的完整实现,玩家控制一个角色(龙)躲避障碍物(仙人掌和乌鸦)。以下是代码的详细介绍:…...

多模态2025:技术路线“神仙打架”,视频生成冲上云霄
文|魏琳华 编|王一粟 一场大会,聚集了中国多模态大模型的“半壁江山”。 智源大会2025为期两天的论坛中,汇集了学界、创业公司和大厂等三方的热门选手,关于多模态的集中讨论达到了前所未有的热度。其中,…...
深入浅出:JavaScript 中的 `window.crypto.getRandomValues()` 方法
深入浅出:JavaScript 中的 window.crypto.getRandomValues() 方法 在现代 Web 开发中,随机数的生成看似简单,却隐藏着许多玄机。无论是生成密码、加密密钥,还是创建安全令牌,随机数的质量直接关系到系统的安全性。Jav…...

【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)
服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...

转转集团旗下首家二手多品类循环仓店“超级转转”开业
6月9日,国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解,“超级…...

PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...
CSS设置元素的宽度根据其内容自动调整
width: fit-content 是 CSS 中的一个属性值,用于设置元素的宽度根据其内容自动调整,确保宽度刚好容纳内容而不会超出。 效果对比 默认情况(width: auto): 块级元素(如 <div>)会占满父容器…...

让回归模型不再被异常值“带跑偏“,MSE和Cauchy损失函数在噪声数据环境下的实战对比
在机器学习的回归分析中,损失函数的选择对模型性能具有决定性影响。均方误差(MSE)作为经典的损失函数,在处理干净数据时表现优异,但在面对包含异常值的噪声数据时,其对大误差的二次惩罚机制往往导致模型参数…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...