前言
作为一个类库作者,迟早会面临接口变更问题,因此接口设计时需要考虑扩展性,保证接口的稳固性,避免接口升级导致旧版本代码不兼容
本篇文章会介绍如何使用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()方法)实际上可以完全不变,透明地应对类库本身的升级动作。