【Golang】编程模式:Functional Options

111 阅读2分钟

前言

作为一个类库作者,迟早会面临接口变更问题,因此接口设计时需要考虑扩展性,保证接口的稳固性,避免接口升级导致旧版本代码不兼容

本篇文章会介绍如何使用Functional Options编程模式来设计一个扩展性很高的接口(方法),以此来帮助大家了解什么是Functional Options编程模式

不使用Functional Options编程模式

想象下有一个早期的类库tut包:

package tut

func New(a int) *Holder {
  return &Holder{
    a: a,
  }
}

type Holder struct {
  a int
}

后来,我们发现需要增加一个布尔变量b,于是修改 tut 库为:

package tut

func New(a int, b bool) *Holder {
  return &Holder{
    a: a,
    b: b,
  }
}

type Holder struct {
  a int
  b bool
}

没过几天,现在我们认为有必要增加一个字符串变量,tut 库不得不被修改为:

package tut

func New(a int, b bool, c string) *Holder {
  return &Holder{
    a: a,
    b: b,
    c: c,
  }
}

type Holder struct {
  a int
  b bool
  c string
}

痛点:
类库的使用者在面对三次New()接口的升级时会很痛苦,需要修改所有使用的地方

下面,我们使用Functional Options编程模式。

使用Functional Options编程模式

假设 tut包 的第一版我们是这样实现的:

package tut

type Opt func (holder *Holder)

func New(opts ...Opt) *Holder {
  h := &Holder{ a: -1, }
  for _, opt := range opts {
    opt(h)
  }
  return h
}

func WithA(a int) Opt {
  return func (holder *Holder) {
        holder.a = a
  }
}

type Holder struct {
      a int
}

// 类库使用者这用使用类库:
func vv(){
  holder := tut.New(tut.WithA(1))
  // ...
}

同样地需求变更发生后,我们将 b 和 c 增加到现有版本上,那么现在的 tut包 看起来是这样的:

package tut

type Opt func (holder *Holder)

func New(opts ...Opt) *Holder {
  h := &Holder{ a: -1, }
  for _, opt := range opts {
    opt(h)
  }
  return h
}

func WithA(a int) Opt {
  return func (holder *Holder) {
    holder.a = a
  }
}

func WithB(b bool) Opt {
  return func (holder *Holder) {
    holder.b = b
  }
}

func WithC(c string) Opt {
  return func (holder *Holder) {
    holder.c = c
  }
}

type Holder struct {
  a int
  b bool
  c string
}

// 类库使用者这样使用类库:
func vv(){
  holder := tut.New(tut.WithA(1), tut.WithB(true), tut.WithC("hello"))
  // ...
}

旧版本类库在用户端的遗留代码(如vv()方法)实际上可以完全不变,透明地应对类库本身的升级动作

后续

【Golang】Functional Options编程第二弹