导读
最近要封装一个公共服务,涉及到配置项的地方总是找不到合理的方案,后来看了一下grpc在配置方面的封装,了解到原来是golang特有的Functional Options编程模式,今天分享给大家,希望你能用到,咱们直接来看代码
版本V1
上面代码很容易,就是想初始化一下Server的配置选项,看起来好像没什么问题,其实问题非常多
- 既然是初始化一些配置选项,那么当然是有的是必选项(Addr, Port),有的是可选项(Timeout,MaxConns),可选项不选的话还得给个默认值,显然这种方式是不满足的
- 因为Server的属性都是公有方法,所以在外部任何地方都能修改属性,存在很严重的代码安全隐患
- 上面Server和main函数虽然在同一个文件里面,其实Server是作为外部包使用的,下面的case都同理 既然上面无法满足咱们的需求,那么咱们就来修改一下
版本V2
既然配置项想要可选,那么咱们直接来个排列组合,调用不同的初始化方法即可只初始化自己想初始化的非必要选项
- Server的属性都是私有变量,的确是解决了包的属性被恶意篡改的行为,降低了代码风险
- 但是排列组合太多,新增一个属性得新增指数级的方法,我上面的demo可选参数只有两个timeout和maxConns,但是如果有十几个可选参数,那么需要构造的初始化方法是非常多的
- 一般情况下,对一个工具初始化都是统一的方法,这样处理的话初始化方法太多了,这一块的内容对使用者来说是不关心的,所以很不友好
- 不想传的参数的默认值依然没有解决
版本V3
既然上面的例子封装的初始化方法太多,那么咱们就统一用一个方法来解决
- 这样做的确是解决了初始化方法太多的问题,但是太不灵活
- 比如有100个可选参数,那而且你只想给最后一个可选参数赋值,但是前面99个你也得写,写的话具体写几?我既然不关心前面99个可选参数,但是为什么还要写呢?这给人感觉就很奇怪
版本V4
咱们引入一个新的结构体Config,把必填的参数放在server里面,非必要的参数放在Congfig里面
- 解决了非必要参数可以有选择性的传一部分的问题,比如上面的case种只需要传Timeout
- 也解决了不传的参数,能有默认值的问题,比如MaxConns不传的话 就是10
- 但是如果只传必传的参数,那么在NewDefaultServer的时候,最后一个参数只能传nil,传nil的情况是不允许的,也是不友好的。
- 用这种方式的话,Config的属性必须是公共变量,当然就有在运行的过程中属性被篡改的风险
版本V5
咱们来学一学java中的builder模式
- 其实就是在Server对象外部包了一层ServerBuilder,最后在ServerBuilder.Build()中返回了Server对象
- 其实这个方法挺完美,满足了我们之前提的全部需求,但是问题在于,golang中的err处理,在这种方式中不是很好体现
版本V6
接下来咱们就看一看最后的终极解决方案 FUNCTIONAL OPTIONS模式
- 这个需要注意的是 type Option func(*Server)
- 这个看起来比较整洁和优雅,对外的接口只有一个Create。
- 相比于Builder模式,不需要引入一个Builder对象。
- 对比配置化的模式,也不需要引入一个新的Config。
总结
Golang 由于语言本身的特性,不支持函数重载,函数式选项 的编程模式在一定程度上解决了其他语言需要通过函数重载解决的问题。函数式选项 编程有以下优点:
- 任意顺序传递参数
- 支持默认值
- 向后兼容性
- 很容易维护和扩展
虽然 函数式选项 编程模式有很多优点,但是设计模式的存在都是为了弥补语言特性的缺陷的一种手段。它是为了解决代码扩展性的问题,往往是通过增加抽象牺牲了简单性,切勿过度使用。有些简单的配置,就不需要设计的这么通用了。
函数式选项模式的使用场景有哪些呢:
我们一般用来配置一些基础的服务配置,比如MySQL,Redis,Kafka的配置,很多可选参数,可以方便动态灵活的配置想要配置的参数。