Generics 对具有基础字段的任何结构数组调用方法

Generics 对具有基础字段的任何结构数组调用方法,generics,go,struct,slice,Generics,Go,Struct,Slice,假设我有一堆结构(大约10个) 如果我在任何给定时间拥有这些结构的数组(无论是[]A,[]B,还是[]C),我如何编写一个从结构数组中提取ID的函数,而不编写3个(或者在我的情况下,10个)这样的单独函数: type AList []A type BList []B type CList []C func (list *AList) GetIDs() []int64 { ... } func (list *BList) GetIDs() []int64 { ... } func (list *

假设我有一堆结构(大约10个)

如果我在任何给定时间拥有这些结构的数组(无论是
[]A
[]B
,还是
[]C
),我如何编写一个从结构数组中提取ID的函数,而不编写3个(或者在我的情况下,10个)这样的单独函数:

type AList []A
type BList []B
type CList []C

func (list *AList) GetIDs() []int64 { ... }
func (list *BList) GetIDs() []int64 { ... }
func (list *CList) GetIDs() []int64 { ... }

通用方法需要使用接口和反射。

通用方法需要使用接口和反射。

据我所知,没有简单的方法

您可能很想使用,但我不确定是否有任何方法可以使此特定任务变得更简单。嵌入感觉像是子类化,但它没有给你多态性的力量

Go中的多态性仅限于方法和接口,而不是字段,因此您不能跨多个类按名称访问给定字段

您可以通过名称(或标记)来查找和访问您感兴趣的字段,但这会导致性能下降,这会使您的代码变得复杂且难以理解。反射并不是真正用来代替多态性或泛型的

我认为您最好的解决方案是使用Go提供的多态性,并创建一个接口:

type IDable interface {
    GetId() int64
}
type IDWrapper struct {
    ID int64
}

func (i IDWrapper) GetID() int64 { return i.ID }

type HasID interface {
    GetID() int64
}

并为每个类创建一个GetId方法

据我所知,没有简单的办法

您可能很想使用,但我不确定是否有任何方法可以使此特定任务变得更简单。嵌入感觉像是子类化,但它没有给你多态性的力量

Go中的多态性仅限于方法和接口,而不是字段,因此您不能跨多个类按名称访问给定字段

您可以通过名称(或标记)来查找和访问您感兴趣的字段,但这会导致性能下降,这会使您的代码变得复杂且难以理解。反射并不是真正用来代替多态性或泛型的

我认为您最好的解决方案是使用Go提供的多态性,并创建一个接口:

type IDable interface {
    GetId() int64
}
type IDWrapper struct {
    ID int64
}

func (i IDWrapper) GetID() int64 { return i.ID }

type HasID interface {
    GetID() int64
}
并为每个类创建一个GetId方法

在切片本身上使用通用方法 如果您定义了一个通用接口来访问切片的
i
th元素的ID,则可以使它变得简单一些:

type HasIDs interface {
    GetID(i int) int64
}
您可以为以下内容提供实现:

func (x AList) GetID(i int) int64 { return x[i].ID }
func (x BList) GetID(i int) int64 { return x[i].ID }
func (x CList) GetID(i int) int64 { return x[i].ID }
然后一个
GetID()
函数就足够了:

func GetIDs(s HasIDs) (ids []int64) {
    ids = make([]int64, reflect.ValueOf(s).Len())
    for i := range ids {
        ids[i] = s.GetID(i)
    }
    return
}
注意:切片的长度可能是
GetIDs()
的一个参数,也可能是
HasIDs
接口的一部分。两者都比获取切片长度的微小反射调用更复杂,所以请耐心听我说

使用它:

as := AList{A{1}, A{2}}
fmt.Println(GetIDs(as))

bs := BList{B{3}, B{4}}
fmt.Println(GetIDs(bs))

cs := []C{C{5}, C{6}}
fmt.Println(GetIDs(CList(cs)))
输出(在上尝试):

注意,我们能够使用类型为
AList
BList
等的切片,我们不需要使用
interface{}
[]SomeIface
。还请注意,我们也可以使用例如a
[]C
,当将其传递给
GetIDs()
时,我们使用了一个简单的类型

这是最简单的。如果您想消除切片的
GetID()
方法,那么您真的需要深入研究反射(包),而且速度会慢一些。上述解决方案的性能与“硬编码”版本大致相同

深思熟虑 如果你想让它完全“通用”,你可以使用反射,然后你就不需要任何额外的方法了

在不检查错误的情况下,以下是解决方案:

func GetIDs(s interface{}) (ids []int64) {
    v := reflect.ValueOf(s)
    ids = make([]int64, v.Len())
    for i := range ids {
        ids[i] = v.Index(i).FieldByName("ID").Int()
    }
    return
}
测试和输出(几乎)相同。请注意,因为这里的
GetIDs()
参数类型是
interface{}
,所以不需要转换为
CList
来传递类型为
[]C
的值。试穿一下

嵌入和反射 通过将字段名称指定为
字符串
来获取字段是非常脆弱的(例如,考虑重命名/重构)。如果我们将
ID
字段和访问器方法“外包”到一个单独的
struct
,我们将嵌入该结构,并通过一个接口捕获访问器,那么我们可以提高可维护性、安全性和反射的性能:

type IDable interface {
    GetId() int64
}
type IDWrapper struct {
    ID int64
}

func (i IDWrapper) GetID() int64 { return i.ID }

type HasID interface {
    GetID() int64
}
并且所有类型都嵌入
IDWrapper

type A struct {
    IDWrapper
}

type B struct {
    IDWrapper
}

type C struct {
    IDWrapper
}
通过嵌入,所有嵌入程序类型(
A
B
C
)都将提升
GetID()
方法,因此它们都会自动实现
HasID
。我们可以在
GetIDs()
函数中利用这一点:

func GetIDs(s interface{}) (ids []int64) {
    v := reflect.ValueOf(s)
    ids = make([]int64, v.Len())
    for i := range ids {
        ids[i] = v.Index(i).Interface().(HasID).GetID()
    }
    return
}
测试它:

as := AList{A{IDWrapper{1}}, A{IDWrapper{2}}}
fmt.Println(GetIDs(as))

bs := BList{B{IDWrapper{3}}, B{IDWrapper{4}}}
fmt.Println(GetIDs(bs))

cs := []C{C{IDWrapper{5}}, C{IDWrapper{6}}}
fmt.Println(GetIDs(cs))
输出是相同的。试穿一下。请注意,在这种情况下,唯一的方法是
IDWrapper.GetID()
,不需要定义其他方法。

使用片本身的常规方法 如果您定义了一个通用接口来访问切片的
i
th元素的ID,则可以使它变得简单一些:

type HasIDs interface {
    GetID(i int) int64
}
您可以为以下内容提供实现:

func (x AList) GetID(i int) int64 { return x[i].ID }
func (x BList) GetID(i int) int64 { return x[i].ID }
func (x CList) GetID(i int) int64 { return x[i].ID }
然后一个
GetID()
函数就足够了:

func GetIDs(s HasIDs) (ids []int64) {
    ids = make([]int64, reflect.ValueOf(s).Len())
    for i := range ids {
        ids[i] = s.GetID(i)
    }
    return
}
注意:切片的长度可能是
GetIDs()
的一个参数,也可能是
HasIDs
接口的一部分。两者都比获取切片长度的微小反射调用更复杂,所以请耐心听我说

使用它:

as := AList{A{1}, A{2}}
fmt.Println(GetIDs(as))

bs := BList{B{3}, B{4}}
fmt.Println(GetIDs(bs))

cs := []C{C{5}, C{6}}
fmt.Println(GetIDs(CList(cs)))
输出(在上尝试):

注意,我们能够使用类型为
AList
BList
等的切片,我们不需要使用
interface{}
[]SomeIface
。还请注意,我们也可以使用例如a
[]C
,当将其传递给
GetIDs()
时,我们使用了一个简单的类型

这是最简单的。如果您想消除切片的
GetID()
方法,那么您真的需要深入研究反射(包),而且速度会慢一些。上述解决方案的性能与“硬编码”版本大致相同

深思熟虑 如果你想让它完全“通用”,你可以使用re