在为不同类型实现功能选项以实现更轻松的配置时,我想分享这些类型之间的一些常见选项。
考虑以下简单示例(其中
name
表示常见的复杂共享类型,id
和 location
表示类型之间的差异):
type A struct{
Name string
ID int
}
type OptionFuncA func(*A)
type B struct{
Name string
Location string
}
type OptionFuncB func(*B)
func NewA(options ...OptionFuncA) A {
obj := A{}
for _, option := range options {
option(&obj)
}
return obj
}
func NewB(options ...OptionFuncB) B {
obj := B{}
for _, option := range options {
option(&obj)
}
return obj
}
在此示例中,我想添加功能选项来设置类型
name
和 A
的 B
。我可以这样做:
type OptionA struct{}
type OptionB struct{}
func (opt OptionA) WithName(name string) OptionFuncA {
return func(a *A) {
a.Name = name
}
}
func (opt OptionB) WithName(name string) OptionFuncB {
return func(b *B) {
b.Name = name
}
}
func (opt OptionA) WithID(id int) OptionFuncA {
return func(a *A) {
a.ID = id
}
}
func (opt OptionB) WithLocation(location string) OptionFuncB {
return func(b *B) {
b.Location = location
}
}
这允许我使用这样的代码:
optA := OptionA{}
a := NewA(optA.WithName("A"), optA.WithID(1))
optB := OptionB{}
b := NewB(optB.WithName("B"), optB.WithLocation("location"))
我想对类型
A
和 B
的不同选项进行类型检查,同时避免对不同类型相同的代码重复。由于函数选项需要分别返回类型 OptionFuncA
和 OptionFuncB
的拟合函数类型 A
和 B
,我想知道是否存在解决方案。
或者,我想定义一个通用接口,不同的
OptionX
类型必须实现:
// Assumption: Option is the common interface
// with options for multiple types.
var _ Option = OptionA{}
var _ Option = OptionB{}
虽然本例中设置
name
的实际代码显然可以共享,但我找不到为两种类型的功能选项定义通用接口的解决方案。功能选项所需的不同返回值不允许从我的角度来看。
是否可以以某种方式强制执行
OptionA
和 OptionB
必须实现一些共享选项,而不失去特定类型的所有其他可用选项的类型安全性?
如果A和B的“Name”含义相同,我认为可以这样组合。
type X struct {
Name string
ID int
Location string
}
func NewA(options ...OptionFuncX) A {
obj := X{}
for _, option := range options {
option(&obj)
}
return A{
Name: obj.Name,
ID: obj.ID,
}
}
所以
WithName
可能是单身。但我想这不是你要找的。
如果这适合您的设计,您可以使用可嵌入结构创建语义构建块。然后可以将统一选项实现为这些嵌入的选项集合。
这种方法通过建立域数据的单一所有者来利用组合并提高代码一致性(例如,
WithName
是名称数据的单一所有者,而不是 A 和 B 拥有自己的名称,但在语法上不相关)。
https://play.golang.org/p/DZMbqXVVurs
package main
import "fmt"
type WithName struct {
FirstName string
LastName string
}
type WithAge struct{ Age int }
type WithID struct{ ID int }
type WithLocation struct{ Location string }
type Option struct {
WithNameFunc func(v *WithName)
WithAgeFunc func(v *WithAge)
WithIDFunc func(v *WithID)
WithLocationFunc func(v *WithLocation)
}
func Name(first, last string) Option {
return Option{
WithNameFunc: func(v *WithName) {
v.FirstName = first
v.LastName = last
},
}
}
func ID(id int) Option {
return Option{
WithIDFunc: func(v *WithID) {
v.ID = id
},
}
}
type A struct {
WithName
WithID
}
type B struct {
WithName
WithAge
WithLocation
}
func NewA(options ...Option) A {
obj := A{}
for _, option := range options {
if option.WithNameFunc != nil {
option.WithNameFunc(&obj.WithName)
}
if option.WithIDFunc != nil {
option.WithIDFunc(&obj.WithID)
}
}
return obj
}
func NewB(options ...Option) B {
obj := B{}
for _, option := range options {
if option.WithNameFunc != nil {
option.WithNameFunc(&obj.WithName)
}
if option.WithAgeFunc != nil {
option.WithAgeFunc(&obj.WithAge)
}
if option.WithLocationFunc != nil {
option.WithLocationFunc(&obj.WithLocation)
}
}
return obj
}
func main() {
a := NewA(Name("John", "Doe"), ID(123))
fmt.Println(a.FirstName, a.LastName, a.ID) // John Doe 123
}
这可能无法解决您当前的问题,但可能对未来的解决方案有所帮助。 https://github.com/kazhuravlev/options-gen 允许您定义结构,运行 gogenerate ./...,并为每个结构字段自动生成 With** 函数。
例如
package example
import "net/http"
//go:generate options-gen -from-struct=Options
type Options struct {
httpClient *http.Client `validate:"required"`
token string
// Address that will be used for each request to the remote server.
addr string
}
在
go generate ./...
之后您将获得几个功能。每个结构字段一个。
package example
func WithHttpClient(opt *http.Client) OptOptionsSetter {
return func(o *Options) { o.httpClient = opt }
}
func WithToken(opt string) OptOptionsSetter {
return func(o *Options) { o.token = opt }
}
// Address that will be used for each request to the remote server.
func WithAddr(opt string) OptOptionsSetter {
return func(o *Options) { o.addr = opt }
}