Closure In Practice

func (c *continuousUpdateEipList) Update() (err error) {
	for _, fn := NewEipListUpdate().steps {
		err := fn()
		if err != nil {
			common.Log.Warn(...)
			return err
		}
	}
	return
}

type Step func() error
type EipListUpdate struct {
	err error
	steps [3]Step
}
func NewEipListUpdate() *EipListUpdate {
	var (
		eipList []EipInfo // 被函数引用形成闭包
		startTime = time.Now()
	)
	initData := func() (err error) {
		eipList, err = initEipList()
		return
	}
	insertData := func() (err error) {
		err = insertEipList(eipList)
		return
	}
	insertAbnormalEvent := func() (err error) {
		err = insertEipAbnormalEvent(eipList, startTime)
		return
	}
	return &EipListUpdate{
		steps: [3]Step{initData, insertData, insertAbnormalEvent},
	}
}
	

Higher-order function

以限流的 interceptor 为例,支持传入自定义的限流器,就需要定义以限流器为参数的高阶函数,返回框架需要的 interceptor,并在 interceptor 函数内使用传入的限流器判断是否需要限流

type Limiter interface {
	Limit(key string) bool
}
func UnaryServerInterceptor(limiter Limiter) grpc.UnaryServerInterceptor {
	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
		if limiter.Limit(info.FullMethod) {
			return nil, status.Errorf(codes.ResourceExhausted, "%s is rejected by grpc_ratelimit middleware, please retry later.", info.FullMethod)
		}
		return handler(ctx, req)
	}
}

目前传入的参数是固定的,可以这么来实现。更进一步,如果使用比较复杂,除了当前已经确定的参数,可以预期以后会增加更多的参数。也就要求当前设计的接口需要有很好的扩展性,使用 Functional Options

type Options struct {
    byMethod  bool
    byUser    bool
    byClientIP bool
}
type Option func(*Options)
// 定义修改配置的一组函数
func ByMethod(m bool) Option {
    return func(o *options) {
        o.byMethod = m
    }
}
func ByUser(u bool) Option {
    return func(o *options) {
        o.byUser = u
    }
}
func ByClientIP(c bool) Option {
    return func(o *options) {
        o.byClientIP = c
    }
}
func UnaryServerInterceptor(limiter Limiter, opts ...Option) grpc.UnaryServerInterceptor {
	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
        default := options {
            byMethod: true,
            byUser: false,
            byClientIP: false,
        }
        for _, opt := range opts {
            opt(&default)
        }
        ...
		return handler(ctx, req)
	}
}

可选参数结构体

type ListAppsOpts struct {
	limit int
	offset int
	hasDeployed bool
}
ListApps(ListAppOpts{limit: 5, offset: 0)
ListApps(ListAppOpts{limit: 5, offset: 0, hasDepolyed: true})

**陷阱:**hasDeployed 是布尔类型,这意味着如果不为其提供任何值时,程序总是会使用布尔类型的零值(zero value):false。所以,现在的代码其实根本拿不到 “未按已部署状态过滤” 的结果,hasDeployed 要么为 true,要么为 false,不存在其他状态

引入指针类型支持可选

为解决上面的问题,最直接的做法是引入 pointer type 和普通的值类型不同,Go 的指针类型拥有一个特殊的零值 nil 因此只要把 hasDeployed 从布尔类型 bool 改成指针类型 *bool

type ListAppOpts struct {
	limit int
	offset int
	hasDeployed *bool
}
func ListApps(opts ListAppOpts) []App {
	if opts.hasDeployed == nil {
		// 默认不过滤的分支
	} else {
		 //按 hasDeployed == true or false 过滤
	}
	...
	return allApps[opts.offset : opts.offset+opts.limit]
}

函数式选项

除了普通传参模式外,Go 语言其实还支持可变数量的参数,使用该特性的函数统称为 “可变参数函数(varadic functions)”。比如 appendfmt.Println 均属此类

为了实现 “函数式选项” 模式,我们首先修改 ListAppss 函数的签名,使其接收类型为 func(*ListAppsOptions) 的可变数量参数