Go语言 用于设置用作Map值的不同结构的字段的泛型函数

5ssjco0h  于 2022-12-16  发布在  Go
关注(0)|答案(3)|浏览(195)

具有公共字段的结构...

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),我已经多次克服了类似的问题,但这次我找不到解决这个问题的方法。
如何将这组函数抽象为单个函数?

jtjikinw

jtjikinw1#

使用包含公共字段的结构来构建结构,并在该公共类型上定义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
    }
}
lymnna71

lymnna712#

泛型适用于相同代码适用于任意数量的类型的情况,例如:

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的所有类型的Map,您真正需要做的只是使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
}

使用接口表示这个函数想要通过一个已知的/定义的接口与你传递给它的对象交互。使用泛型表示你期望提供将作为一个整体使用的数据。设置字段并不是将数据作为一个整体使用,因此我认为泛型不是这项工作的正确工具。泛型 * 可以 * 非常强大,在某些情况下非常有用。不久前,我在代码回顾上发表了一篇关于泛型创建并发安全Map的评论,这是一个很好的泛型用例,以至于我最终实现了这样一个类型作为回应,并将其放在github
我想我应该提到这一点,我一点也不反对泛型。它们可能非常有用。我反对的是过度使用这个特性,这可能--而且经常--导致代码发臭,更难阅读/维护。

db2dz4w8

db2dz4w83#

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
如果要求使用 * 值 * 的Map,则@blackgreen答案的替代方案。
一个一个三个一个一个一个一个一个四个一个一个一个一个一个五个一个
https://go.dev/play/p/AG050b0peFw

相关问题