具有公共字段的结构…
type Definition struct {
Id string
...
}
type Requirement struct {
Id string
...
}
type Campaign struct {
Id string
...
}
…我有这样的多个函数:
func fillDefinitionIds(values *map[string]Definition) {
for key, value:=range *values { // Repeated code
value.Id=key // Repeated code
(*values)[key]=value // Repeated code
} // Repeated code
}
func fillRequirementIds(values *map[string]Requirement) {
for key, value:=range *values { // Repeated code
value.Id=key // Repeated code
(*values)[key]=value // Repeated code
} // Repeated code
}
func fillCampaignIds(values *map[string]Campaign) {
for key, value:=range *values { // Repeated code
value.Id=key // Repeated code
(*values)[key]=value // Repeated code
} // Repeated code
}
我想有一个单一的函数,用泛型(或接口,无论什么)泛化访问,有点…
func fillIds[T Definition|Requirement|Campaign](values *map[string]T) {
for key, value:=range *values {
value.Id=key
(*values)[key]=value
}
}
当然,这就得到了value.Id undefined (type T has no field or method Id)
。类似的问题我已经解决过很多次了,但这次我找不到解决办法。
如何将这组函数抽象为一个函数?
用一个包含公共字段的结构体组合你的结构体,并在该公共类型上定义一个setter方法:
type Base struct {
Id string
}
func (b *Base) SetId(id string) {
b.Id = id
}
type Definition struct {
Base
}
type Requirement struct {
Base
}
type Campaign struct {
Base
}
然后将接口约束定义为指针类型的联合,并指定setter方法。你必须这样做,因为在当前版本的Go中,泛型字段访问是不可用的。
type IDer interface {
*Definition | *Requirement | *Campaign
SetId(id string)
}
func fillIds[T IDer](values map[string]T) {
for key, value := range values {
value.SetId(key)
values[key] = value
}
}
示例:https://go.dev/play/p/fJhyhazyeyc
func main() {
m1 := map[string]*Definition{"foo": {}, "bar": {}}
fillIds(m1)
for _, v := range m1 {
fmt.Println("m1", v.Id)
// foo
// bar
}
m2 := map[string]*Campaign{"abc": {}, "def": {}}
fillIds(m2)
for _, v := range m2 {
fmt.Println("m2", v.Id)
// abc
// def
}
}
type Definition struct {
Id string
}
type Requirement struct {
Id string
}
type Campaign struct {
Id string
}
func (v Definition) WithId(id string) Definition { v.Id = id; return v }
func (v Requirement) WithId(id string) Requirement { v.Id = id; return v }
func (v Campaign) WithId(id string) Campaign { v.Id = id; return v }
type WithId[T any] interface {
WithId(id string) T
}
func fillIds[T WithId[T]](values map[string]T) {
for key, value := range values {
values[key] = value.WithId(key)
}
}
func main() {
m1 := map[string]Definition{"foo": {}, "bar": {}}
fillIds(m1)
fmt.Println(m1)
m2 := map[string]Campaign{"abc": {}, "def": {}}
fillIds(m2)
fmt.Println(m2)
}
https://go.dev/play/p/F3Qk0gcyKEa
如果需要使用值的映射,则可以替代@blackgreen的答案。
type Common struct {
Id string
}
func (v *Common) SetId(id string) { v.Id = id }
type Definition struct {
Common
}
type Requirement struct {
Common
}
type Campaign struct {
Common
}
type IdSetter[T any] interface {
*T
SetId(id string)
}
func fillIds[T any, U IdSetter[T]](values map[string]T) {
for key, value := range values {
U(&value).SetId(key)
values[key] = value
}
}
func main() {
m1 := map[string]Definition{"foo": {}, "bar": {}}
fillIds(m1)
fmt.Println(m1)
m2 := map[string]Campaign{"abc": {}, "def": {}}
fillIds(m2)
fmt.Println(m2)
}
https://go.dev/play/p/AG050b0peFw
泛型用于相同的代码适用于任意数量的类型的情况,例如:
func Ptr[T any](v T) *T {
return &v
}
如果您希望使用泛型来实际修改许多不同类型中的特定字段,那么泛型并不是真正可行的方法。从本质上讲,这不是它们的用途,而golang已经有了允许您这样做的特性:组合和接口。
你已经确定了共享字段,很好,所以创建一个类型并在需要的地方嵌入它:
type Common struct {
ID string
}
type Foo struct {
Common
FooSpecificField int64
}
type Bar struct {
Common
BarOnly string
}
现在在通用类型上添加setter:
func (c *Common) SetID(id string) {
c.ID = id
}
现在所有嵌入Common
的类型都有一个ID
字段,并有一个setter:
f := Foo{}
f.SetID("fooID")
fmt.Println(f.ID) // fooID
b := Bar{}
b.SetID("barID")
fmt.Println(b.ID) // barID
要接受允许您设置ID的所有类型的映射,您真正需要做的就是使fillIds
接受所需的接口:
type IDs interface {
SetID(string)
}
func fillIDs(vals map[string]IDs) map[string]IDs {
for k, v := range vals {
v.SetID(k)
vals[k] = v
}
return vals
}
因为根据定义,setter应该是指针接收器,所以你可以把同样的函数写得更短:
func fillIDs(vals map[string]IDs) map[string]IDs {
for k := range vals {
vals[k].SetID(k)
}
return vals
}
使用接口表明该函数希望通过已知/定义的接口与传递给它的对象进行交互。使用泛型表明您希望提供将作为一个整体使用的数据。设置字段并不是将数据作为一个整体来使用,因此我认为泛型不是这项工作的正确工具。泛型可以非常强大,在某些情况下非常有用。不久前,我发布了一篇关于创建并发安全映射的泛型代码审查的评论。这是一个很好的泛型用例,以至于我最终实现了这样的类型作为响应,并把它放在github上
我想我应该提一下,我一点也不反对泛型。它们可能非常有用。我反对的是过度使用这个特性,这可能——而且经常会——导致代码很臭,更难阅读/维护。