从别人的代码中吸取养分!让自己成长

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@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 要如何来改善呢?

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

按照上面的五步大法,你就能够实现设置默认值的高阶玩法。

如果你喜欢这个类型的文章,欢迎留言点赞!