PART1. 概念示例
1.1 问题的产生
假设我们现在需要定义一个包含多个配置项的结构体,具体定义如下:
Copy package pattern
type SomeOption struct {
A string
B int
C bool
}
这个配置结构体中的字段可能有几个也可能有十几个,并且可能随着业务的发展不断增加新的字段.现在我们需要为其编写一个构造函数,根据经验我们可能会写出类似下面那样的构造函数:
Copy package pattern
type SomeOption struct {
A string
B int
C bool
}
// NewSomeOption SomeOption 的构造函数
func NewSomeOption (a string , b int , c bool ) * SomeOption {
return & SomeOption {
A: a,
B: b,
C: c,
}
}
这样的设计面临着2个问题:
如果SomeOption
有十几个字段,那我们的构造函数需要定义十几个参数吗?如何为某些配置项指定默认值?
SomeOption
随着业务发展不断新增字段后,我们的构造函数是否也需要同步变更?变更了构造函数是否又会影响既有代码?
1.2 函数选项模式
函数选项模式(Functional Options Pattern)也称为选项模式(Options Pattern),是一种创建型设计模式 ,选项模式允许你使用接受0个或多个函数作为可变参数的构造函数 ,以便构建复杂结构.我们将这些函数称为选项,由此得名函数选项模式
1.2.1 可选参数
由于Go语言中的函数不支持默认参数,所以我们想到可以使用可变长参数 来实现.而这个可变长参数的具体类型则需要好好设计一下.它必须满足以下条件:
Copy package pattern
// OptionFunc 函数选项 该类型用于设置 SomeOption 的各属性值(即各选项)
type OptionFunc func ( * SomeOption )
OptionFunc
类型本质上是一个函数.该函数接收一个*SomeOption
作为参数,并在其内部修改这个实例的字段值:
Copy package pattern
// OptionFunc 函数选项 该类型用于设置 SomeOption 的各属性值(即各选项)
type OptionFunc func ( * SomeOption )
// WithB 本函数返回一个 OptionFunc ,该 OptionFunc 将
// SomeOption 实例的 B 字段值设置为给定值
func WithB (b int ) OptionFunc {
return func (someOption * SomeOption ) {
someOption.B = b
}
}
// WithC 本函数返回一个 OptionFunc ,该 OptionFunc 将
// SomeOption 实例的 C 字段值设置为给定值
func WithC (c bool ) OptionFunc {
return func (someOption * SomeOption ) {
someOption.C = c
}
}
那么这样一来,我们的构造函数就可以修改为:
Copy package pattern
type SomeOption struct {
A string
B int
C bool
}
// NewSomeOption SomeOption 的构造函数
func NewSomeOption (a string , options ... OptionFunc ) * SomeOption {
someOption := & SomeOption {
A: a,
}
for _, option := range options {
option (someOption)
}
return someOption
}
最终创建SomeOption
实例时的客户端代码就会简单很多:
Copy package main
import (
"fmt"
"option/pattern"
)
func main () {
// 初始化SomeOption时,若SomeOption的字段有变化,仅需调整options切片即可
options := [] pattern . OptionFunc {
pattern. WithB ( 10 ),
pattern. WithC ( true ),
}
someOption := pattern. NewSomeOption ( "a" , options ... )
fmt. Printf ( " %#v \n" , someOption)
}
假如此时需求变更,不再需要初始化SomeOption
实例时为SomeOption.B
字段赋值,那么变更就会容易很多:
Copy package main
import (
"fmt"
"option/pattern"
)
func main () {
options := [] pattern . OptionFunc {
pattern. WithC ( true ),
}
someOption := pattern. NewSomeOption ( "a" , options ... )
fmt. Printf ( " %#v \n" , someOption)
}
1.2.2 默认值问题
通常我们在初始化实例这个场景下,对默认值的理解是:客户端提供了值,则使用客户端提供的;客户端没提供,则使用默认值.
在函数选项模式中,这种功能就非常容易提供了:
Copy package pattern
type SomeOption struct {
A string
B int
C bool
}
const (
// defaultValueB SomeOption 实例的 B 字段的默认值
defaultValueB = 100
)
// NewSomeOption SomeOption 的构造函数
func NewSomeOption (a string , options ... OptionFunc ) * SomeOption {
someOption := & SomeOption {
A: a,
B: defaultValueB,
}
// 若客户端提供了修改B字段的选项函数 则在此处会覆盖掉默认值
for _, option := range options {
option (someOption)
}
return someOption
}
这样一来,就可以实现提供默认值的需求了.
1.3 接口类型的函数选项模式
在一些场景下,我们可能并不想对外暴露具体的配置结构体(也就是我们上述示例中的SomeOption
),而是仅仅对外提供一个功能函数.这时我们会将对应的结构体定义为小写字母开头,将其限制只在包内部使用:
Copy package pattern
type someOption struct {
a string
b int
c bool
}
1.3.1 定义接口
Copy package pattern
// IOption 本接口用于定义函数选项行为
type IOption interface {
// apply 本方法用于修改 someOption 结构体的字段值
apply (someOption * someOption )
}
1.3.2 定义实现
Copy package pattern
// funcOption 函数选项类型 因为要实现接口,所以不能再使用函数类型,只能使用结构体
type funcOption struct {
// f 具体选项函数
withFunc func (someOption * someOption )
}
func (o * funcOption ) apply (someOption * someOption ) {
o. withFunc (someOption)
}
1.3.3 定义函数选项类型的构造函数
Copy package pattern
// funcOption 函数选项类型 因为要实现接口,所以不能再使用函数类型,只能使用结构体
type funcOption struct {
// f 具体选项函数
withFunc func (someOption * someOption )
}
func (o * funcOption ) apply (someOption * someOption ) {
o. withFunc (someOption)
}
// newFuncOption funcOption 的构造函数
func newFuncOption (withFunc func (someOption * someOption )) * funcOption {
return & funcOption {withFunc: withFunc}
}
1.3.4 定义各种With()
函数
注意各种With()
函数是要对外暴露的,因为客户端要通过这些With()
函数来修改someOption
实例的各属性值
Copy package pattern
// WithB 本函数用于修改 someOption 实例的 b 字段值
func WithB (b int ) IOption {
withFunc := func (someOption * someOption ) {
someOption.b = b
}
return & funcOption {withFunc: withFunc}
}
// WithC 本函数用于修改 someOption 实例的 c 字段值
func WithC (c bool ) IOption {
withFunc := func (someOption * someOption ) {
someOption.c = c
}
return & funcOption {withFunc: withFunc}
}
1.3.5 实现对外提供的功能函数
这个功能函数的职责其实和1.2小节中客户端代码的职责是相同的,都是实例化 someOption
Copy package pattern
import "fmt"
// NewSomeOption 本函数是 someOption 的构造函数
func NewSomeOption (a string , options ... IOption ) {
someOptionObj := & someOption {
a: a,
}
for _, option := range options {
option. apply (someOptionObj)
}
fmt. Printf ( " %#v \n" , someOptionObj)
}
1.3.6 客户端代码
Copy package main
import "optionWithInterface/pattern"
func main () {
options := [] pattern . IOption {
pattern. WithB ( 10 ),
pattern. WithC ( true ),
}
pattern. NewSomeOption ( "a" , options ... )
}
可以看到,使用函数选项模式时,若要实例化的对象上,字段发生了变化,仅需调整options
即可.
Last updated 8 months ago