随着业务的发展,golang方法的参数可能越来越多,每次修改都需要在最后新增一个参数,请求参数就会越来越多,时间一长代码可读性差,修改之前的参数还可以导致改错,导致线上问题。
Context通常被译作上下文,它是一个比较抽象的概念。一般理解为程序单元的一个运行状态、现场、快照,而翻译中上下又很好地诠释了其本质,上下则是存在上下层的传递,上会把内容传递给下。在golang的一层层调用中,context是一直存在的,因此考虑使用context来避免上述问题。
我们可以定义一个配置结构体,使用context.WithValue()方法,将配置结构体塞到context中,然后通过context.Value()来断言出配置的结构体,即可解决。
package options
import (
"context"
"fmt"
)
type demoConfig struct {
demoInt int64
demoBool bool
demoString string
}
type Option func(*demoConfig)
func DemoInt(in int64) Option {
return func(dc *demoConfig) {
dc.demoInt = in
}
}
func DemoBool(in bool) Option {
return func(dc *demoConfig) {
dc.demoBool = in
}
}
func DemoString(in string) Option {
return func(dc *demoConfig) {
dc.demoString = in
}
}
func (dc *demoConfig) Apply(opts ...Option) {
for _, opt := range opts {
opt(dc)
}
}
type demoConfigKey struct {}
// 赋值到context
func WithContext(ctx context.Context, cfg demoConfig) context.Context {
return context.WithValue(ctx, demoConfigKey{}, cfg)
}
type createFunc func() demoConfig
// 取出context中的config
func FromContextOrCreate(ctx context.Context, create createFunc) demoConfig {
vc, ok := ctx.Value(demoConfigKey{}).(demoConfig)
if !ok {
return create()
}
return vc
}
type Service struct {}
func NewService() *Service {
return &Service{}
}
func (s *Service) DefaultDemoConfigCreater() createFunc {
return func() demoConfig {
return demoConfig{
// default val
demoString: "hello",
}
}
}
func (s *Service) Demo(ctx context.Context) {
cfg := FromContextOrCreate(ctx, s.DefaultDemoConfigCreater())
// print: {27 false hello}
fmt.Println(cfg)
}
package main
import (
"context"
"params-with-ctx/options"
)
func main() {
svc := options.NewService()
cfg := svc.DefaultDemoConfigCreater()()
opts := []options.Option{
options.DemoInt(27),
}
cfg.Apply(opts...)
ctx := context.Background()
ctx = options.WithContext(ctx, cfg)
svc.Demo(ctx)
}
在代码中广泛使用到go的function type,当然,也可以不将参数都塞到context中,如一个简单方法需要传递一些带有特定业务意义true/false的参数,来决策方法内的拼接,此时也可直接定义一个function type,使代码具有更强的可读性,同时后续的修改也可以清楚的得知参数的含义,而不是一行全是一堆true/false。
type config struct {
needSwitch bool
needWatchLater bool
}
type Option func(*config)
func NeedSwitch(in bool) Option {
return func(cfg *config) {
cfg.needSwitch = in
}
}
func (cfg *config) Apply(opts ...Option) {
for _, opt := range opts {
opt(cfg)
}
}
func constructThreePoint(ctx context.Context, opts ...Option) {
cfg := &config{}
cfg.Apply(opts...)
// 直接使用
}
//调用
// constructThreePoint(ctx, NeedSwitch(true))