前言
通过这篇文章《为什么说 Go 的函数是”一等公民“》,咱们理解到了什么是“一等公民”,以及都具备哪些个性,同时对函数的根本应用也更加深刻。
本文重点介绍下 Go 设计模式之 函数选项模式,它得益于 Go 的函数是“一等公民”,很好的一个利用场景,宽泛被应用。
什么是函数选项模式
函数选项模式(Functional Options Pattern),也称为选项模式(Options Pattern),是一种创造性的设计模式,容许你应用 承受零个或多个函数作为参数 的可变构造函数来构建简单构造。咱们将这些函数称为选项,由此得名函数选项模式。
看概念有点太僵硬难懂了,上面通过例子来解说下怎么应用,由浅入深,通俗易懂。
怎么应用函数选项模式
个别程度
先来一个简略例子,这个Animal 构造体
,怎么结构出一个实例对象
type Animal struct {
Name string
Age int
Height int
}
通常的写法:
func NewAnimal(name string, age int, height int) *Animal {
return &Animal{
Name: name,
Age: age,
Height: height,
}
}
a1 := NewAnimal("小白兔", 5, 100)
简略易懂,构造体有哪些属性字段,那么构造函数的参数,就相应做定义并传入
带来的问题:
- 代码耦合度高 :加属性字段,构造函数就得 相应做批改,调用的中央全副都得改,势必会影响现有代码;
- 代码灵便度低 :属性字段不能指定 默认值,每次都得明确传入;
例如,现打算新加 3 个字段Weight 体重
、CanRun 是否会跑
、LegNum 几条腿
,同时要指定默认值CanRun=true、LegNum=4
新构造体定义:
type Animal struct {
Name string
Age int
Height int
Weight int
CanRun bool
LegNum int
}
代码实现(函数加新参数定义,但默认值貌似实现不了,得调用构造函数时,明确传入):
func NewAnimal(name string, age int, height int, weight int, canRun bool, legNum int) *Animal {
return &Animal{
Name: name,
Age: age,
Height: height,
Weight: weight,
CanRun: canRun,
LegNum: legNum,
}
}
a1 := NewAnimal("小白兔", 5, 100, 120, true, 4)
后续逐渐加新字段,这个构造函数就会被撑爆了,如果调用的中央越多,那么越伤筋动骨。
高阶程度
既然惯例写法太 low,难以实现新需要,那么咱们就来玩点高阶的,引出主题:函数选项模式
首先,须要先定义一个函数类型OptionFunc
type OptionFunc func(*Animal)
而后 ,依据新构造体字段,定义With 结尾
的函数,返回函数类型为 OptionFunc
的闭包函数,外部逻辑只须要实现更新对应字段值即可
func WithName(name string) OptionFunc {return func(a *Animal) {a.Name = name}
}
func WithAge(age int) OptionFunc {return func(a *Animal) {a.Age = age}
}
func WithHeight(height int) OptionFunc {return func(a *Animal) {a.Height = height}
}
func WithWeight(weight int) OptionFunc {return func(a *Animal) {a.Weight = weight}
}
func WithCanRun(canRun bool) OptionFunc {return func(a *Animal) {a.CanRun = canRun}
}
func WithLegNum(legNum int) OptionFunc {return func(a *Animal) {a.LegNum = legNum}
}
再而后 ,优化构造函数的定义和实现(name 作为必传参数,其余可选,并且实现CanRun
和LegNum
两个字段指定默认值)
func NewAnimal(name string, opts ...OptionFunc) *Animal {a := &Animal{Name: name, CanRun: true, LegNum: 4}
for _, opt := range opts {opt(a)
}
return a
}
最初 ,调用优化后的构造函数,疾速实现实例的初始化。想要指定哪个字段值,那就调用相应的With 结尾
的函数,齐全做到可配置化、可插拔;不指定还反对了默认值
a2 := NewAnimal("大黄狗", WithAge(10), WithHeight(120))
fmt.Println(a2)
a3 := NewAnimal("大灰狼", WithHeight(200))
fmt.Println(a3)
输入后果:&{大黄狗 10 120 0 true 4}
&{大灰狼 0 200 0 true 4}
带来的益处:
- 高度的可配置化、可插拔,还反对默认值设定;
- 很容易保护和扩大;
- 容易上手,大幅升高新来的人试错老本;
开源我的项目中的实际案例
函数选项模式,不单单是咱们业务代码中有应用,当初大量的规范库和第三库都在应用。
上面带着大家一块来看看,apollo 配置核心客户端第三库 shima-park/agollo,看看它是怎么玩的,怎么做配置初始化
外围代码:
type Options struct {
AppID string // appid
Cluster string // 默认的集群名称,默认:default
DefaultNamespace string // Get 时默认应用的命名空间,如果设置了该值,而不在 PreloadNamespaces 中,默认也会退出初始化逻辑中
PreloadNamespaces []string // 预加载命名空间,默认:为空
ApolloClient ApolloClient // apollo HTTP api 实现
Logger Logger // 日志实现类,能够设置自定义实现或者通过 NewLogger()创立并设置无效的 io.Writer,默认: ioutil.Discard
AutoFetchOnCacheMiss bool // 主动获取非预设以外的 Namespace 的配置,默认:false
LongPollerInterval time.Duration // 轮训间隔时间,默认:1s
BackupFile string // 备份文件寄存地址,默认:.agollo
FailTolerantOnBackupExists bool // 服务器连贯失败时容许读取备份,默认:false
Balancer Balancer // ConfigServer 负载平衡
EnableSLB bool // 启用 ConfigServer 负载平衡
RefreshIntervalInSecond time.Duration // ConfigServer 刷新距离
ClientOptions []ApolloClientOption // 设置 apollo HTTP api 的配置项
EnableHeartBeat bool // 是否容许兜底查看,默认:false
HeartBeatInterval time.Duration // 兜底查看间隔时间,默认:300s
}
func newOptions(configServerURL, appID string, opts ...Option) (Options, error) {
var options = Options{
AppID: appID,
Cluster: defaultCluster,
ApolloClient: NewApolloClient(),
Logger: NewLogger(),
AutoFetchOnCacheMiss: defaultAutoFetchOnCacheMiss,
LongPollerInterval: defaultLongPollInterval,
BackupFile: defaultBackupFile,
FailTolerantOnBackupExists: defaultFailTolerantOnBackupExists,
EnableSLB: defaultEnableSLB,
EnableHeartBeat: defaultEnableHeartBeat,
HeartBeatInterval: defaultHeartBeatInterval,
}
for _, opt := range opts {opt(&options)
}
//... 省略
return options, nil
}
type Option func(*Options)
// 一系列函数作为选项
func PreloadNamespaces(namespaces ...string) Option {return func(o *Options) {o.PreloadNamespaces = append(o.PreloadNamespaces, namespaces...)
}
}
func AutoFetchOnCacheMiss() Option {return func(o *Options) {o.AutoFetchOnCacheMiss = true}
}
//...
玩法:
- 应用 Options 构造体,定义 出 apollo 须要应用到的 所有配置字段;
- 定义 一系列函数作为选项,对配置字段做初始化设置(例如,设置容灾文件门路、预加载的 namespace、轮训间隔时间等等);
- 构造函数里 初始化一个 Options 的实例对象,并且依据传入的函数选项,进行配置字段的更新,最终返回这个实例对象;
- 获取到实例对象,调用相应的办法做相应的操作。
总结
由浅入深的解说了下实例对象初始化个别写法和高阶写法。用好这个高阶写法(函数选项模式),让代码更高比格。还不会应用的 Gopher,连忙学起来,用起来。
如果本文对你有帮忙,欢送点赞珍藏加关注,如果本文有谬误的中央,欢送指出!
本文参加了 SegmentFault 思否写作挑战赛,欢送正在浏览的你也退出。