Generics 对具有基础字段的任何结构数组调用方法
假设我有一堆结构(大约10个) 如果我在任何给定时间拥有这些结构的数组(无论是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 *
[]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