GRPC
GolangPHPPython

低阶玩家应对默认值问题

CartExtsGolang
extext1

下面看下代码具体会怎么做

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, "")
}

CartExtsCartExts
CartExtsCartExtsNewCartCartExts
GRPC

GRPC 之高阶玩家设置默认值

源码来自:grpc@v1.28.1 版本。为了突出主要目标,对代码进行了必要的删减。


// dialOptions 详细定义在 google.golang.org/grpc/dialoptions.go
type dialOptions struct {
// ... ...
insecure bool
timeout time.Duration
// ... ...
}

// ClientConn 详细定义在 google.golang.org/grpc/clientconn.go
type ClientConn struct {
// ... ...
authority string
dopts dialOptions // 这是我们关注的重点,所有可选项字段都在这里
csMgr *connectivityStateManager

// ... ...
}

// 创建一个 grpc 链接
func DialContext(ctx context.Context, target string, opts ...DialOption) (conn *ClientConn, err error) {
cc := &ClientConn{
target: target,
csMgr: &connectivityStateManager{},
conns: make(map[*addrConn]struct{}),
dopts: defaultDialOptions(), // 默认值选项
blockingpicker: newPickerWrapper(),
czData: new(channelzData),
firstResolveEvent: grpcsync.NewEvent(),
}
// ... ...

// 修改改选为用户的默认值
for _, opt := range opts {
opt.apply(&cc.dopts)
}
// ... ...
}
DialContextClientConndefaultDialOptions
defaultDialOptions

DialOption 的封装

DialOption
type DialOption interface {
apply(*dialOptions)
}
*dialOptions&cc.doptsapply
// 空实现,什么也不做
type EmptyDialOption struct{}

func (EmptyDialOption) apply(*dialOptions) {}

// 用到最多的地方,重点讲
type funcDialOption struct {
f func(*dialOptions)
}

func (fdo *funcDialOption) apply(do *dialOptions) {
fdo.f(do)
}

func newFuncDialOption(f func(*dialOptions)) *funcDialOption {
return &funcDialOption{
f: f,
}
}
funcDialOptionDialOption
newFuncDialOptionfuncDialOptionf*dialOptionsapply
applyfuncDialOptionapplyfnewFuncDialOption
newFuncDialOption

newFuncDialOption 的调用

newFuncDialOption*funcDialOptionDialOptiongrpc.DialContext
insecuretimeout

// 以下方法详细定义在 google.golang.org/grpc/dialoptions.go
// 开启不安全传输
func WithInsecure() DialOption {
return newFuncDialOption(func(o *dialOptions) {
o.insecure = true
})
}

// 设置 timeout
func WithTimeout(d time.Duration) DialOption {
return newFuncDialOption(func(o *dialOptions) {
o.timeout = d
})
}

来体验一下这里的精妙设计:

DialOptiongrpc.DialContext*funcDialOptionDialOption

grpc.DialContext 的调用

完成了上面的程序构建,现在我们来站在使用的角度,感受一下这无限的风情。


opts := []grpc.DialOption{
grpc.WithTimeout(1000),
grpc.WithInsecure(),
}

conn, err := grpc.DialContext(context.Background(), target, opts...)
// ... ...
optsDialOption*funcDialOptionDialOption
grpc.DialContextapply
// 修改改选为用户的默认值
for _, opt := range opts {
opt.apply(&cc.dopts)
}

经过这样一层层的包装,虽然增加了不少代码量,但是明显能够感受到整个代码的美感、可扩展性都得到了改善。接下来看一下,我们自己的 demo 要如何来改善呢?

改善 DEMO 代码

CartExtscartExts

const (
CommonCart = "common"
BuyNowCart = "buyNow"
)

type cartExts struct {
CartType string
TTL time.Duration
}

type CartExt interface {
apply(*cartExts)
}

// 这里新增了类型,标记这个函数。相关技巧后面介绍
type tempFunc func(*cartExts)

// 实现 CartExt 接口
type funcCartExt struct {
f tempFunc
}

// 实现的接口
func (fdo *funcCartExt) apply(e *cartExts) {
fdo.f(e)
}

func newFuncCartExt(f tempFunc) *funcCartExt {
return &funcCartExt{f: f}
}

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, exts ...CartExt) *DemoCart {
c := &DemoCart{
UserID: userID,
Sku: Sku,
Ext: DefaultExt, // 设置默认值
}

// 遍历进行设置
for _, ext := range exts {
ext.apply(&c.Ext)
}

return c
}

cartExts

func WithCartType(cartType string) CartExt {
return newFuncCartExt(func(exts *cartExts) {
exts.CartType = cartType
})
}

func WithTTL(d time.Duration) CartExt {
return newFuncCartExt(func(exts *cartExts) {
exts.TTL = d
})
}

对于使用者来说,只需如下处理:

exts := []CartExt{
WithCartType(CommonCart),
WithTTL(1000),
}

NewCart("dayu", 888, exts...)

总结

是不是非常简单?我们再一起来总结一下这里代码的构建技巧:

2

按照上面的五步大法,你就能够实现设置默认值的高阶玩法。
如果你喜欢这个类型的文章,欢迎留言点赞!