最近使用 GRPC
发现一个设计特别好的地方,非常值得借鉴。
我们在日常写方法的时候,希望给某个字段设置一个默认值,不需要定制化的场景就不传这个参数,但是 Golang
却没有提供像 PHP
、Python
这种动态语言设置方法参数默认值的能力。
低阶玩家应对默认值问题
以一个购物车举例。比如我有下面这样一个购物车的结构体,其中 CartExts
是扩展属性,它有自己的默认值,使用者希望如果不改变默认值时就不传该参数。但是由于 Golang
无法在参数中设置默认值,只有以下几个选择:
- 提供一个初始化函数,所有的
ext
字段都做为参数,如果不需要的时候传该类型的零值,这把复杂度暴露给调用者; - 将
ext
这个结构体做为一个参数在初始化函数中,与1
一样,复杂度在于调用者; - 提供多个初始化函数,针对每个场景都进行内部默认值设置。
下面看下代码具体会怎么做
const (
CommonCart = "common"
BuyNowCart = "buyNow"
)
type CartExts struct {
CartType string
TTL time.Duration
}
type DemoCart struct {
UserID string
ItemID string
Sku int64
Ext CartExts
}
var DefaultExt = CartExts{
CartType: CommonCart, // 默认是普通购物车类型
TTL: time.Minute * 60, // 默认 60min 过期
}
// 方式一:每个扩展数据都做为参数
func NewCart(userID string, Sku int64, TTL time.Duration, cartType string) *DemoCart {
ext := DefaultExt
if TTL > 0 {
ext.TTL = TTL
}
if cartType == BuyNowCart {
ext.CartType = cartType
}
return &DemoCart{
UserID: userID,
Sku: Sku,
Ext: ext,
}
}
// 方式二:多个场景的独立初始化函数;方式二会依赖一个基础的函数
func NewCartScenes01(userID string, Sku int64, cartType string) *DemoCart {
return NewCart(userID, Sku, time.Minute*60, cartType)
}
func NewCartScenes02(userID string, Sku int64, TTL time.Duration) *DemoCart {
return NewCart(userID, Sku, TTL, "")
}
上面的代码看起来没什么问题,但是我们设计代码最重要的考虑就是稳定与变化,我们需要做到 对扩展开放,对修改关闭 以及代码的 高内聚。那么如果是上面的代码,你在 CartExts
增加了一个字段或者减少了一个字段。是不是每个地方都需要进行修改呢?又或者 CartExts
如果有非常多的字段,这个不同场景的构造函数是不是得写非常多个?所以简要概述一下上面的办法存在的问题。
- 不方便对
CartExts
字段进行扩展; - 如果