golang设计模式--函数选项模式

138 阅读3分钟

1.结构体的实例化

type dbOptions struct {
   Host     string
   Port     int
   UserName string
   Password string
   DBName   string
}

func main() {
    //这是最常见的结构体实例化方式
   db := &dbOptions{
      Host:     "127.0.0.1",
      Port:     3306,
      UserName: "xhz",
      Password: "root",
      DBName:   "mysql",
   }
}

假设现在有一个需求:
通过一个New函数,返回结构体实例化,不带参数会有默认值,带参数就对应更新默认值 如果不考虑设计模式的写法,那可能最好想的就是下面的方式:

unc NewDBclient(Host string, Port int, UserName string, Password string, DBName string) *dbOptions{
   dbopts := &dbOptions{
      Host:     "127.0.0.1",
      Port:     3306,
      UserName: "xhz",
      Password: "root",
      DBName:   "mysql",
   }
   if Host != "" {
      dbopts.Host = Host
   }
   if Port != 0 {
      dbopts.Port = Port
   }
   if UserName != "" {
      dbopts.UserName = UserName
   }
   if dbopts.Password != "" { 
      dbopts.Password = Password
   }
   if dbopts.DBName != "" {
      dbopts.DBName = DBName
   }
   return dbopts
}
//这种写法虽然可以满足需求,但是很明显代码显得十分累赘的感觉
//可以想象,如果一个结构体有几十个字段,那岂不是意味着几十个字段都要判断,而且传参都要传
//这么看来,这个方法还需改善一番

在开发过程中,其实不难发现很多第三方包会经常通过一个New函数来实例化出结构体
并且在 New 函数的入参会经常出现一些 withXXX( ) 的函数eg: NewXXXX(withXXX( ))
注意看 NewXXXX( )的参数就是一些函数选项,这种参数是函数选项的模式是 golang 特有的模式 也称之为 函数选项模式

2.函数选项模式

type dbOptions struct {
   Host     string
   Port     int
   UserName string
   Password string
   DBName   string
}

// Option 给函数类型取别名,从这里可以看出,函数的内部逻辑是没有限制的
type Option func(*dbOptions)
//函数选项模式的 New 函数
func NewDBclient(opt ...Option) *dbOptions {
//设置好默认的返回值
   dbopts := &dbOptions{
      Host:     "127.0.0.1",
      Port:     3306,
      UserName: "xhz",
      Password: "root",
      DBName:   "mysql",
   }
   //根据传入的一个个函数来更新返回值的字段
   for _, v := range opt {
      v(dbopts) //每一个函数选项都是接收一个结构体指针
   }
   return dbopts
}

到此 NewDBclient 函数就完成了,但是可以发现更新结构体字段是严格依靠 opt 的内部逻辑的
所以现在的任务是构造出一个 func(*dbOptions) 样子的函数即可eg:

type Option func(*dbOptions)
func withF1(db *dbOptions) {
   db.Host = "1234567890"
}

上面是符合 func(*dbOptions)的样子了,但是很明显把 Host 写死了
所以,还需要把 Host 调整到入参的位置,类似于下面这样:

type Option func(*dbOptions)
func withF1(Host string){}

但是还需要满足func(*dbOptions)的样子要求,所以只能将返回值设计成func(*dbOptions)

type Option func(*dbOptions)
func withF2(Host string) Option {
   return func(db *dbOptions) {
      db.Host = Host
   }
}

最终的代码

package main

import "fmt"

// 需求:通过一个New函数,可以返回自己想要的结构体指针,并且不带参数也会有默认值,有参数就对应改
type dbOptions struct {
   Host     string
   Port     int
   UserName string
   Password string
   DBName   string
}

// Option 给函数取别名,从这里可以看出,函数的内部逻辑是没有限制的
type Option func(*dbOptions)

func NewDBclient(opt ...Option) *dbOptions {
   dbopts := &dbOptions{
      Host:     "127.0.0.1",
      Port:     3306,
      UserName: "xhz",
      Password: "root",
      DBName:   "mysql",
   }
   for _, v := range opt {
      v(dbopts) //每一个函数选项都是接收一个结构体指针
   }
   return dbopts
}

// withHost 的返回值恰好是 NewDBclient 的入参
func withHost(host string) Option {
   return func(o *dbOptions) {
      o.Host = host
   }
}
func main() {
   re := NewDBclient(withHost("localhost"))
   fmt.Printf("%#v\n", re)
}

例子中的 withHost( ) 函数只是展示了更新 Host 一个字段的值,所以完全可以根据需求定义其他的with函数,eg:withHost( ) withPort( ) withUserNameAndPassword( )...