函数的不定参数之Options设计模式


有时候一个函数会有很多参数,为了方便函数的使用,我们会给希望给一些参数设定默认值,调用时只需要传与默认值不同的参数即可,类似于 python 里面的默认参数和字典参数,虽然 golang 里面既没有默认参数也没有字典参数,但是我们有选项模式。

案例

// NewFriend 寻找志同道合朋友
func NewFriend(sex int, age int, hobby string) (string, error) {

    // 逻辑处理 ...

    return "", nil
}

NewFriend(),方法中参数 sexage 为非必传参数,这时方法如何怎么写?

传参使用不定参数!

想一想怎么去实现它?

看一下这样写可以吗?

// Sex 性别
type Sex int

// Age 年龄
type Age int

// NewFriend 寻找志同道合的朋友
func NewFriend(hobby string, args ...interface{}) (string, error) {
    for _, arg := range args {
        switch arg.(type) {
        case Sex:
            fmt.Println(arg, "is sex")
        case Age:
            fmt.Println(arg, "is age")
        default:
            fmt.Println("未知的类型")
        }
    }
    return "", nil
}

有没有更好的方案呢?

传递结构体… 恩,这也是一个办法。

咱们看看别人的开源代码怎么写的呢,我学习的是 grpc.Dial(target string, opts …DialOption) 方法,它都是通过 WithXX 方法进行传递的参数,例如:

conn, err := grpc.Dial("127.0.0.1:8000",
    grpc.WithChainStreamInterceptor(),
    grpc.WithInsecure(),
    grpc.WithBlock(),
    grpc.WithDisableRetry(),
)

比着葫芦画瓢,我实现的是这样的,大家可以看看:

// Option custom setup config
type Option func(*option)

// option 参数配置项
type option struct {
    sex int
    age int
}

// NewFriend 寻找志同道合的朋友
func NewFriend(hobby string, opts ...Option) (string, error) {
    opt := new(option)
    for _, f := range opts {
        f(opt)
    }

    fmt.Println(opt.sex, "is sex")
    fmt.Println(opt.age, "is age")

    return "", nil
}

// WithSex sex 1=female 2=male
func WithSex(sex int) Option {
    return func(opt *option) {
        opt.sex = sex
    }
}

// WithAge age
func WithAge(age int) Option {
    return func(opt *option) {
        opt.age = age
    }
}

使用的时候这样调用:

friends, err := friend.NewFriend(
    "看书",
    friend.WithAge(30),
    friend.WithSex(1),
)

if err != nil {
    fmt.Println(friends)
}

这样写如果新增其他参数,是不是也很好配置呀。

实战

在 以下代码中,使用到了选项模式。

package conf

import (
    "www.example.com/backend/golang/go-log/utils"
    "go.uber.org/zap/zapcore"
)

type Option func(*Options)

type Options struct {
    LogFile        string //日志保存文件
    LogLevel       level  //日志记录级别
    Stacktrace     level  //记录堆栈的级别
    TimeLayout     string //时间格式
    DisableConsole bool   //是否标准输出console输出
    ProjectName    string //项目名称
}

func CreateWriteSyncer(file string) zapcore.WriteSyncer {
    f := utils.CreateFileP(file)
    return zapcore.Lock(f)
}

func WithLogFileP(logFile string) Option {
    return func(opt *Options) {
        if logFile != "" {
            opt.LogFile = logFile
        }
    }
}

func WithLogLevel(logLevel level) Option {
    return func(opt *Options) {
        opt.LogLevel = logLevel
    }
}

func WithStacktrace(stacktrace level) Option {
    return func(o *Options) {
        o.Stacktrace = stacktrace
    }
}

// WithTimeLayout custom time format
func WithTimeLayout(timeLayout string) Option {
    return func(opt *Options) {
        opt.TimeLayout = timeLayout
    }
}

// WithDisableConsole WithEnableConsole write log to os.Stdout or os.Stderr
func WithDisableConsole() Option {
    return func(opt *Options) {
        opt.DisableConsole = true
    }
}

func WithProjectName(projectName string) Option {
    return func(opt *Options) {
        opt.ProjectName = projectName
    }
}

Options模式优缺点

  • 优点1) 支持传递多个参数,并且在参数个数、类型发生变化试保持兼容性2) 任意顺序传递参数3) 支持默认值

    4) 方便扩展

  • 缺点1) 增加许多function,成本增大2) 参数不复杂时,尽量少用
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。