本文的行文思路

  1. 提出问题
  2. 提出初步解决方案
  3. 优化初步解决方案
  4. 演进到函数可选项式编程模式

一、配置问题

在Server这个结构体中,我们可以看到:

AddrPortProtocolTimeoutMaxConnstcp301024TLS

所以针对不同可选项的配置,有多种创建Server的函数签名。

因为Go语言不支持函数重载,所以必须用不同的函数名来对应不同的配置可选项。

缺点:

1、太多创建Server的函数,程序员不知道调用哪个好

2、会写很多冗余代码

3、代码的可扩展性不好


二、如何解决

2、1 分离可选项

很认可“六月天天”博客上说的这句话,编程的一大重点,就是“分离变化点和不变点”。

将非必填字段放入Config结构体中,于是Server结构体就变为了

于是我们只需要一个NewServer()的函数,在使用前需要构造Config对象。

ConfignilConfig{}

除了“分离可选项”这种方法外,还有其他的方法吗?

2、2 Builder模式

使用一个builder类来包装Server,然后分两步操作:

1、Create方法根据必填项addr和port参数来创建ServerBuilder实例

2、WithProtocol、WithMaxConn、WithTimeOut、WithTLS方法将函数参数传递给builder类中的Server对象

3、提供Build()方法将builder类中的Server对象暴露出来

于是就可以以如下的方式来使用了

有不少开源代码是采用此种方式进行的。

使用链式的函数调用的方式来构造一个对象,只需要多加一个Builder类。

缺点:

Builder类似乎有点多余,能否省去呢?

如果想省点这个包装的结构体,那么就轮到函数选项登场了。

2、3 函数选项模式

函数选项模式的实现,主要有两种

  • 基于闭包实现
  • 基于接口实现

2、3、1 基于闭包的实现

1)定义一个函数类型Option

type Option func(*Server)


2)闭关方式定义函数

这里对函数参数并未做校验(感兴趣的小伙伴可以自行实现)

以WithMaxConns函数为例,传入一个参数maxConn,返回一个函数,这个函数会将参数maxConn赋值给Server的对应的MaxConns字段。

  • 当我们调用其中的一个函数用WithMaxConns(30)时
  • 其返回值是一个func(s *Server) { s.MaxConns = 30 }的函数


3)定义NewServer()函数,

函数参数是必填字段和可选字段(可变参数类型)

首先,NewServer函数的opts是可变参数,可以传递上面的WithProtocol、WithTimeout、WithMaxConns等函数中的一个或多个。

然后,创建Server对象,并填写可选项的默认值,如Timeout超时时间为10秒。

最后,for-loop循环opts可变参数,执行函数option去设置s对象中的各个属性。完毕!


4)测试NewServer()函数

运行结果如下:


2、3、2 基于接口的实现

1)定义接口

Option接口定义了apply(server *Server)方法,然后可选项需要实现接口


2)可选项实现接口

每个可选项都需要实现接口

以可选项protocol的ProtoOption为例:

自定义类型ProtoOption实现了Option接口。


3)封装函数将可选项包装成接口类型

提供WithProtocol函数将string类型的proto转换为ProtoOption类型,方便以Option接口的形式传递给函数使用


4) 定义NewServer函数

for-loop循环opts可变参数,执行接口中的apply发那个发去设置s对象中的各个属性,记得s一定要传递指针,否则无法修改对象属性!


三、总结

本文就配置可选项问题,进行了深入浅出的分析,提出了三种解决方案,分别是

  • 分离可选项
  • Builder模式
  • 函数选项模式

在平时阅读开源项目过程中,以Builder模式和函数选项模式居多,函数选项模式的优点还是挺多的。


优点:

可读性强,将配置都转化成了对应的函数项option

扩展性好,新增参数只需要增加一个对应的方法

缺点:

需编写多个Option函数,代码量会有所增加


参考链接: