Go编程模式之Options

124 阅读2分钟

What(什么是Options模式)

  • Options模式是Java Builder模式在Go语言中的简易写法
  • Options模式得益于Go语言中函数是一等公民,可做为另一个函数的入参和返回
  • Options模式用于为某个对象进行相关配置

Why(为什么需要Options模式)

先来看这样一个场景:

我们在业务内部经常会调用其他的API获取数据,为了简化调用,我们做了简单的封装,如下代码所示

type APICall struct {
   ip   string
   port string
}

func NewAPICall(ip string, port string) *APICall {
   return &APICall{
      ip:   ip,
      port: port,
   }
}

func (a *APICall) Call(reqParam []byte) ([]byte, error) {
   //业务逻辑
   return nil, nil
}

接下来会这样调用API

func GetToken(ip string, port string, key []byte) ([]byte, error) {
   c := NewAPICall(ip,port)
   return c.Call(key)
}

现在,我们为APICall增加设置超时时间、日志记录入参和出参的功能,使用方可以选择使用。可能会这样改造APICall

type APICall struct {
   ip   string
   port string
}

func NewAPICall(ip string, port string) *APICall {
   return &APICall{
      ip:   ip,
      port: port,
   }
}

func (a *APICall) Call(p []byte) ([]byte, error) {
   //业务逻辑
   return nil, nil
}

func (a *APICall) CallWithTimeout(p []byte,timeout time.Duration) ([]byte, error) {
   //业务逻辑+timeout逻辑
   return nil, nil
}

func (a *APICall) CallWithLog(p []byte) ([]byte, error) {
   //业务逻辑+printlog逻辑
   return nil, nil
}

我们发现这样扩展函数的方式有几个问题:

  1. 当我们增加更多的功能时,我们需要提供更多的函数,同时适用方调用也不方便
  2. 我们可能需要同时设置超时超时和打印日志,此时我们需要提供的函数数量是笛卡尔积
  3. 不符合面向对象的思想

从根源上解决这个问题,我们可以将timeout、printlog等做为APICall的属性(符合面向对象的设计),同时利用Options模式,只需要提供一个call函数即可

How(如何使用Options模式)

所谓模式、模型,都是前人总结好的,有着特定《套路》的。

第一步,定义函数类型

type Option func(*APICall)

第二步,给APICall添加属性

type APICall struct {
   ip       string
   port     string
   timeout  time.Duration
   printLog bool
}

第三步,定义函数,返回Option

func WithTimeout(t time.Duration) Option {
   return func(call *APICall) {
      call.timeout = t
   }
}

func WithPrintLog() Option {
   return func(call *APICall) {
      call.printLog = true
   }
}

第四步,改造NewAPICall,入参增加Options,同时改造Call函数,适配新增的属性逻辑

func NewAPICall(ip string, port string, options ...Option) *APICall {
   instance := &APICall{
      ip:   ip,
      port: port,
   }
   for _, option := range options {
      option(instance)
   }
   return instance
}

func (a *APICall) Call(p []byte) ([]byte, error) {
   //业务逻辑
   //timeout逻辑
   //printlog逻辑
   return nil, nil
}

第五步,调用

func GetToken(ip string, port string, key []byte) ([]byte, error) {
   c := NewAPICall(ip, port, WithPrintLog(), WithTimeout(time.Second))
   return c.Call(key)
}