分类
Go

Go 中的 Options 模式

Functional Options Pattern(函数式选项模式)可用于传递不同选项配置到方法中,而且每次新增选项时,可以不改变接口保持兼容。还可以用来实现类似 Java/C++ 中方法重写的功能~

Go 中结构体没有构造方法,所以有的时候我们会自己提供一个 New 方法。考虑你有某个客户端结构体:

type Client struct {
	// ...
}

func New() *Client {
	return &Client{}
}

有一天需要新增一个 Timeout 参数,由于 Go 不支持方法重载,因此我们不得不修改 New 方法传入一个 Timeout 参数, 但是一旦修改了 New 的签名,将会造成不兼容。于是我们只好新增一个工厂方法 NewClientWithTimeout 并且修改旧有的 New 方法,设置它的 Timeout 为一个默认值。

但是下次再需要新增字段,还是很麻烦……

我们可以使用可变参数来解决这一问题。

先定义一个配置结构体:

type Config struct {
   Timeout time.Duration
   Cluster string
}
type Option func(*Config)

然后对每个配置项提供一个配置函数

func WithTimeout(timeout time.Duration) Option {
	return func(config *Config) {
		config.Timeout = timeout
	}
}
func WithCluster(cluster string) Option {
	return func(config *Config) {
		config.Cluster = cluster
	}
}

最后在 New 方法上使用可变参数来接收配置函数

func New(opts ...Option) *Client {
	// default config
	config := &Config{Timeout: 20 * time.Millisecond, Cluster: "default"}
	// for each custom options
	for _, opt := range opts {
		opt(config)
	}
	return &Client{
		Timeout: config.Timeout,
		Cluster: config.Cluster,
	}
}

下次再需要新增配置项,只需要新增 WithXxx 配置函数,修改默认 Config 的值即可。调用方可以直接升级版本,如果不传入新的配置函数,则会使用默认配置。

New()
New(WithTimeout(10 * time.Millisecond))
New(WithTimeout(10*time.Millisecond), WithCluster("my-cluster"))

这个模式还可以用来模仿 Java8 的接口默认实现、或者用来模仿 Java 中的子类重写(override)父类方法。

package foo

// 暴露的接口
type MyInterface interface {
	SayHi() string
}

// 实现类
type base struct {
	// 得益于 Go 中函数是一等公民,可以将函数存在字段中
	sayHi func() string
}

// 使用函数字段实现接口
func (b *base) SayHi() string {
	return b.sayHi()
}

// 提供自定义能力
type Option func(*base)

func SayHi(f func() string) Option {
	return func(b *base) {
		b.sayHi = f
	}
}

// 默认行为
func newDefault() *base {
	return &base{
		sayHi: func() string {
			return "Hello, World"
		},
	}
}

// 构造方法
func New(opts ...Option) MyInterface {
	b := newDefault()
	for _, opt := range opts {
		opt(b)
	}
	return b
}

使用方:

func ExampleNew() {
	fmt.Println(foo.New().SayHi())
	fmt.Println(foo.New(foo.SayHi(func() string {
		return "你好,世界"
	})).SayHi())
	// Output:
	// Hello, World
	// 你好,世界
}

这样就可以通过传入函数的形式改变默认的行为,看起来像重写😂

Go Streams 仿照 Java8 的流造的轮子》中 stage 接口使用了这一模式。


发表评论

电子邮件地址不会被公开。 必填项已用*标注

[/鼓掌] [/难过] [/调皮] [/白眼] [/疑问] [/流泪] [/流汗] [/撇嘴] [/抠鼻] [/惊讶] [/微笑] [/得意] [/大兵] [/坏笑] [/呲牙] [/吓到] [/可爱] [/发怒] [/发呆] [/偷笑] [/亲亲]

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据