有时候一个函数会有很多参数,为了方便函数的使用,我们会给希望给一些参数设定默认值,调用时只需要传与默认值不同的参数即可,类似于 python 里面的默认参数和字典参数,虽然 golang 里面既没有默认参数也没有字典参数,但是我们有选项模式。
可变长参数列表
在这之前,首先需要介绍一下可变长参数列表,顾名思义,就是参数的个数不固定,可以是一个也可以是多个,最典型的用法就是标准库里面的 fmt.Printf
,语法比较简单,如下面例子实现任意多个参数的加法
1 2 3 4 5 6 7 8 9 func add(nums ...int) int { sum := 0 for _, num := range nums { sum += num } return sum } So(add(1, 2), ShouldEqual, 3) So(add(1, 2, 3), ShouldEqual, 6)
在类型前面加 ...
来表示这个类型的变长参数列表,使用上把参数当成 slice
来用即可
选项模式
假设我们要实现这样一个函数,这个函数接受5个参数,三个 string
(其中第一个参数是必填参数),两个 int
,这里功能只是简单输出这个参数,于是我们可以简单用如下代码实现
1 2 3 4 5 func MyFunc1(requiredStr string, str1 string, str2 string, int1 int, int2 int) { fmt.Println(requiredStr, str1, str2, int1, int2) } // 调用方法 MyFunc1("requiredStr", "defaultStr1", "defaultStr2", 1, 2)
这种实现比较简单,但是同时传入参数较多,对调用方来说,使用的成本就会比较高,而且每个参数的具体含义这里并不清晰,很容易出错
那选项模式怎么实现这个需求呢?先来看下最终的效果
1 2 3 MyFunc2("requiredStr") MyFunc2("requiredStr", WithOptionStr1("mystr1")) MyFunc2("requiredStr", WithOptionStr2AndInt2("mystr2", 22), WithOptionInt1(11))
如上面代码所示,你可以根据自己的需求选择你需要传入的参数,大大简化了函数调用的复杂度,并且每个参数都有了清晰明确的含义
那怎么实现上面的功能呢
定义可选项和默认值
首先定义可选项和默认值,这里有4个可选项,第一个参数为必填项
1 2 3 4 5 6 7 8 9 10 11 12 type MyFuncOptions struct { optionStr1 string optionStr2 string optionInt1 int optionInt2 int } var defaultMyFuncOptions = MyFuncOptions{ optionStr1: "defaultStr1", optionStr2: "defaultStr2", optionInt1: 1, optionInt2: 2, }
实现 With 方法
这些 With 方法看起来有些古怪,接受一个选项参数,返回一个选项方法,而选项方法以选项作为参数负责修改选项的值,如果没看明白没关系,可以先看函数功能如何实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 type MyFuncOption func(options *MyFuncOptions) func WithOptionStr1(str1 string) MyFuncOption { return func(options *MyFuncOptions) { options.optionStr1 = str1 } } func WithOptionInt1(int1 int) MyFuncOption { return func(options *MyFuncOptions) { options.optionInt1 = int1 } } func WithOptionStr2AndInt2(str2 string, int2 int) MyFuncOption { return func(options *MyFuncOptions) { options.optionStr2 = str2 options.optionInt2 = int2 } }
这里我们让 optionStr2 和 optionInt2 合并一起设置,实际应用场景中可以用这种方式将相关的参数放到一起设置
实现函数功能
1 2 3 4 5 6 7 func MyFunc2(requiredStr string, opts ...MyFuncOption) { options := defaultMyFuncOptions for _, o := range opts { o(&options) } fmt.Println(requiredStr, options.optionStr1, options.optionStr2, options.optionInt1, options.optionInt2) }
使用 With 方法返回的选项方法作为参数列表,用这些方法去设置选项。
转载自:golang 设计模式之选项模式
将代码直接拿出运行即可知道其中奥妙,这里面合理运行了Go的可变长参数特性。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 package mainimport ( "fmt" ) type MyFuncOptions struct { optionStr1 string optionStr2 string optionInt1 int optionInt2 int } var defaultMyFuncOptions = MyFuncOptions{ optionStr1: "defaultStr1" , optionStr2: "defaultStr2" , optionInt1: 1 , optionInt2: 2 , } type MyFuncOption func (options *MyFuncOptions) func WithOptionStr1 (str1 string ) MyFuncOption { return func (options *MyFuncOptions) { options.optionStr1 = str1 } } func WithOptionInt1 (int1 int ) MyFuncOption { return func (options *MyFuncOptions) { options.optionInt1 = int1 } } func WithOptionStr2AndInt2 (str2 string , int2 int ) MyFuncOption { return func (options *MyFuncOptions) { options.optionStr2 = str2 options.optionInt2 = int2 } } func MyFunc2 (requiredStr string , opts ...MyFuncOption) { options := defaultMyFuncOptions for _, o := range opts { o(&options) } fmt.Println(requiredStr, options.optionStr1, options.optionStr2, options.optionInt1, options.optionInt2) } func main () { MyFunc2("requiredStr" ) MyFunc2("requiredStr" , WithOptionStr1("mystr1" )) MyFunc2("requiredStr" , WithOptionStr2AndInt2("mystr2" , 22 ), WithOptionInt1(11 )) }
GRPC
1 2 grpc.Dial(addr, grpc.WithInsecure()) grpc.Dial(addr, grpc.WithInsecure(), grpc.WithTimeout(time.Duration(10 *time.Second)))
在grpc
中,这样的设计在google.golang.org/grpc/clientconn.go
的Dial(target string, opts ...DialOption)
,其中DialOption
各函数在google.golang.org/grpc/dialoptions.go
中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 func Dial (target string , opts ...DialOption) (*ClientConn, error) { return DialContext(context.Background(), target, opts...) } func DialContext (ctx context.Context, target string , opts ...DialOption) (conn *ClientConn, err error) { cc := &ClientConn{ target: target, csMgr: &connectivityStateManager{}, conns: make (map [*addrConn]struct {}), dopts: defaultDialOptions(), blockingpicker: newPickerWrapper(), czData: new (channelzData), firstResolveEvent: grpcsync.NewEvent(), } ... for _, opt := range opts { opt.apply(&cc.dopts) } ... }
而这些函数设计思路和上面讲述是一样的。
1 2 3 4 5 6 7 8 9 10 11 12 13 func WithDisableRetry () DialOption { return newFuncDialOption(func (o *dialOptions) { o.disableRetry = true }) } ... func WithTimeout (d time.Duration) DialOption { return newFuncDialOption(func (o *dialOptions) { o.timeout = d }) }
此外,当我们需要加入的新的参数时,我们只需要加入新函数选项,以前的调用则给定一个默认值。
函数式选择模式比较常见的用法即是在GRPC
上所用,在其他开源基本也是如此用法。
本文标题: 函数选择模式(Functional Options Patter)
文章作者: 小师
发布时间: 2019-07-17
最后更新: 2022-05-04
原始链接: chunlife.top/2019/07/17/函数选择模式-Functional-Options-Patter/
版权声明: 本站所有文章均采用知识共享署名4.0国际许可协议进行许可