装饰器模式
装饰器模式:允许向一个现有的对象添加新的功能,同时又不改变其结构
C++ 中典型例子是继承+多态,可以自己百度下
这里介绍 golang 实际项目中用到的,达到装饰器模式同样效果的例子
摘自作者自己的描述是:
It’s a design pattern that allows the addition of new options without changing the method signature.
该例子来至: github.com/micro/go-micro
实际效果
func main() {
// 装饰器模式在这里:
// micro.NewService 定义接口保持不变
// 可以接受任意数量`选项对象`,达成设置、打开各种功能,或其他初始化操作
service := micro.NewService(
micro.Name("com.example.srv.foo"), // 这是个`选项对象`
micro.RegisterTTL(time.Second*30), // 这是个`选项对象`
micro.RegisterInterval(time.Second*15), // 这是个`选项对象`
)
service.Init()
if err := service.Run(); err != nil {
log.Fatal(err)
}
}
实现细节
相关代码主要分布在:
- https://github.com/micro/go-micro/blob/master/micro.go
- https://github.com/micro/go-micro/blob/master/options.go
实现细节摘录如下:
type Option func(*Options) // `选项对象`实际类型为函数, 这是重点
func NewService(opts ...Option) Service { // 接口保持不变,类似 AddOption 其实也可以,没这个简洁
return newService(opts...)
}
func newService(opts ...Option) Service {
options := newOptions(opts...)
// ... 无关代码略 ...
return &service{
opts: options,
}
}
func newOptions(opts ...Option) Options {
// 初始化 opt 对象,设置初始值
opt := Options{
Broker: broker.DefaultBroker,
Cmd: cmd.DefaultCmd,
Client: client.DefaultClient,
Server: server.DefaultServer,
Registry: registry.DefaultRegistry,
Transport: transport.DefaultTransport,
Context: context.Background(),
}
// 这里是重点,`选项对象`在这里生效
for _, o := range opts {
o(&opt)
}
return opt
}
// 具体某个`选项对象`
func Name(n string) Option {
// 返回函数对象
return func(o *Options) {
o.Server.Init(server.Name(n))
}
}
代码分析
1. Option 类型
Option 是个函数对象
类型
这样自由度很大,函数体内可以实现你的意图,比如 Name
这个 Option :
func Name(n string) Option {
return func(o *Options) {
o.Server.Init(server.Name(n))
}
}
Name
这个 Option , 实现对 o.Server 对象初始化时,赋值名字
2. NewService(opts …Option) 接口定义
这样的接口定义,即参数可变,又保持接口定义不变
比如 AddOption(opt Option) 本质上也是可以的,但没 NewService 接口简洁
3. Option 函数对象生效代码
for _, o := range opts {
o(&opt)
}
opt 保持着所有需要初始化的对象,opts 内是所有 Option 对象
上述语句,让所有 Option 对象实实在在作用于 opt 上
关于生效时机,看上述代码在什么函数调用,比如 NewService 中调用,即构造对象时生效
也可以在诸如 Init() 这种函数内调用
Option 实现的优缺点
- 优点
- 使用界面相当简洁
- 用法优美
- 缺点
- 实现细节稍繁琐
- 每个 Option 对象要写段函数
以上