Go 设计模式:选项模式学习

284 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 6 天,点击查看活动详情

Go 选项模式学习

一、什么是选项模式 ?

  • 选项模式,全称为函数选项模式。是一种创造型设计模式,可让构造函数接受零个或者多个函数式选项当做可变参数来构建复杂的结构。

二、为什么要用选项模式(适用场景) ?

功能优点:

  1. Golang 不支持默认值参数,而选项模式可以提供类似的功能。
  2. 支持传递多个参数,在创建复杂结构对象时(如:domain object),比 builder(建造者or生成器) 模式在使用中更方便且优雅。
  3. 支持任意顺序传递参数,并方便扩展。

适用场景

  1. 结构体参数比较多,创建结构体时需要携带默认值,并且支持修改某些参数的值。
  2. 结构体参数变动较多,变动时不想过多的修改构造函数。

三、选项模式是如何工作的 ?

以简化版为例说明:

  1. 默认选项功能:通过 const 设置默认值,并在构造函数NewConnect中先创建一个带默认值参数的Connection 结构体。
  2. 自定义选项功能
    • 1)定义函数式参数类型 type Option func(* Connection) ,并通过 WithXXX 函数来创建函数式选项参数,如:WithTimeOut。函数内有*Connection结构体的操作。
    • 2)通过 for 循环,让传入构造函数NewConnect中的opts选项参数,对*Connection结构体进行循环赋值。

四、如何使用选项模式 ?

示例:以初始化 Connect 结构体实例为例子,看选项模式如何应用(两种方式效果相同)

1. 将 option 抽象成接口的形式

package main  
  
import (  
   "fmt"  
   "time"
)  

// 默认参数常量
const (  
   defaultTimeout = 10  
   defaultCaching = false  
)  

// Connection 结构体
type Connection struct {  
   addr    string  
   cache   bool  
   timeout time.Duration  
}  

// 参数结构体(用于自定义参数设置)
type options struct {  
   timeout time.Duration  
   caching bool  
}  

// 参数接口(通过接口强制实现 apply 方法)
type Option interface {  
   apply(*options)  
}  

// 参数函数(用于实现 Option 接口的类型)
type optionFunc func(*options)  

// 参数函数的方法(通过 apply 方法,完成对 options 结构体的赋值)
func (f optionFunc) apply(o *options) {  
   f(o)  
}  

// 自定义参数的函数(返回 Option 接口类型,
// 通过 optionFunc 转换匿名函数 func(*options*) 
// 为 optionFunc 类型并实现 Option 接口,匿名函数内是结构体的参数设置)
func WithTimeout(t time.Duration) Option {  
   return optionFunc(func(o *options) {  
     o.timeout = t   
   })
}  
  
func WithCaching(cache bool) Option {  
   return optionFunc(func(o *options) {  
      o.caching = cache  
   })  
}  

// 构造函数(opts 支持接收多个选项参数)
func NewConnect(addr string, opts ...Option) (*Connection, error) {  
   options := options{  
      timeout: defaultTimeout,  
      caching: defaultCaching,  
   }  
  
   // 循环对 options 结构体赋值
   for _, o := range opts {  
      o.apply(&options)  
   }  
   return &Connection{  
      addr:    addr,  
      cache:   options.caching,  
      timeout: options.timeout,  
   }, nil  
}  
  
func main() {  
   addr := "10.0.0.1"  
   c, _ := NewConnect(addr, WithTimeout(100), WithCaching(true)) 
   fmt.Printf("%+v\n", c)  
}

抽象成接口的这种方式,我认为好处在于可在 apply 方法中,对参数进行一些集中的操作,方便维护。

2. 将 option 自定义成函数类型(简化版)

package main  
  
import (  
   "fmt"  
   "time")  
  
const (  
   defaultTimeout = 10  
   defaultCaching = false  
)  
  
type Connection struct {  
   addr    string  
   cache   bool  
   timeout time.Duration  
}  
  
type Option func(*Connection)  
  
func WithTimeout(t time.Duration) Option {  
   return func(c *Connection) {  
      c.timeout = t  
   }  
}  
  
func WithCaching(cache bool) Option {  
   return func(c *Connection) {  
      c.cache = cache  
   }  
}  
  
func NewConnect(addr string, opts ...Option) (*Connection, error) {  
   c := &Connection{  
      addr:    addr,  
      timeout: defaultTimeout,  
      cache:   defaultCaching,  
   }  
  
   for _, o := range opts {  
      o(c)  
   }  
   return c, nil  
}  
  
func main() {  
   addr := "10.0.0.1"  
   c, _ := NewConnect(addr, WithTimeout(100), WithCaching(true))  
   fmt.Printf("%+v\n", c)  
}

简化版更方便理解,在一般场景上功能也完全够用。

五、参考资料

  1. Go 语言项目开发实战(非广告)
  2. GO 编程模式:FUNCTIONAL OPTIONS
  3. Functional Options Pattern in Golang

开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 6 天,点击查看活动详情