在具有基础字段的任何结构数组上调用方法
Call method on any array of structs that have underlying field
假设我有一堆结构(大约 10 个)。
type A struct {
ID int64
... other A-specific fields
}
type B struct {
ID int64
... other B-specific fields
}
type C struct {
ID int64
... other C-specific fields
}
如果我在任何给定时间都有这些结构的数组([]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 { ... }
泛型方法需要使用接口和反射。
据我所知,没有简单的方法。
您可能想使用 embedding,但我不确定是否有任何方法可以使这项特定任务变得更容易。嵌入感觉就像子类化,但它并没有给你多态性的力量。
Go 中的多态性仅限于方法和接口,而不是字段,因此您不能跨多个 类.
按名称访问给定字段
您可以使用 reflection 通过名称(或标签)查找和访问您感兴趣的字段,但这样做会降低性能,并且会使您的代码变得复杂且难以理解。反射并不是真正打算替代多态或泛型。
我认为你最好的解决方案是使用 Go 为你提供的多态性,并创建一个接口:
type IDable interface {
GetId() int64
}
并为每个 类 创建一个 GetId 方法。 Full example.
使用切片本身的通用方法
如果您定义一个通用接口来访问切片的第i
个元素的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)))
输出(在 Go Playground 上尝试):
[1 2]
[3 4]
[5 6]
请注意,我们可以使用 AList
、BList
等类型的切片,我们不需要使用 interface{}
或 []SomeIface
。另请注意,我们也可以使用例如a []C
,传给GetIDs()
时,我们用了一个简单的类型conversion.
这很简单。如果你想消除切片的 GetID()
方法,那么你真的需要更深入地研究反射(reflect
包),它会更慢。上面提供的解决方案与 "hard-coded" 版本的性能大致相同。
完全反射
如果你希望它完全"generic",你可以使用反射来完成,然后你绝对不需要任何额外的方法。
不检查错误,这里是解决方案:
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
的值。在 Go Playground.
上试用
有嵌入和反射
通过将其名称指定为 string
来获取字段非常脆弱(例如考虑重命名/重构)。如果我们 "outsource" ID
字段和一个访问器方法到一个单独的 struct
,我们可以提高可维护性、安全性和反射的性能,我们将嵌入它,并捕获访问器通过界面:
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))
输出相同。在 Go Playground 上试试吧。请注意,在这种情况下,唯一的方法是 IDWrapper.GetID()
,不需要定义其他方法。
假设我有一堆结构(大约 10 个)。
type A struct {
ID int64
... other A-specific fields
}
type B struct {
ID int64
... other B-specific fields
}
type C struct {
ID int64
... other C-specific fields
}
如果我在任何给定时间都有这些结构的数组([]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 { ... }
泛型方法需要使用接口和反射。
据我所知,没有简单的方法。
您可能想使用 embedding,但我不确定是否有任何方法可以使这项特定任务变得更容易。嵌入感觉就像子类化,但它并没有给你多态性的力量。
Go 中的多态性仅限于方法和接口,而不是字段,因此您不能跨多个 类.
按名称访问给定字段您可以使用 reflection 通过名称(或标签)查找和访问您感兴趣的字段,但这样做会降低性能,并且会使您的代码变得复杂且难以理解。反射并不是真正打算替代多态或泛型。
我认为你最好的解决方案是使用 Go 为你提供的多态性,并创建一个接口:
type IDable interface {
GetId() int64
}
并为每个 类 创建一个 GetId 方法。 Full example.
使用切片本身的通用方法
如果您定义一个通用接口来访问切片的第i
个元素的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)))
输出(在 Go Playground 上尝试):
[1 2]
[3 4]
[5 6]
请注意,我们可以使用 AList
、BList
等类型的切片,我们不需要使用 interface{}
或 []SomeIface
。另请注意,我们也可以使用例如a []C
,传给GetIDs()
时,我们用了一个简单的类型conversion.
这很简单。如果你想消除切片的 GetID()
方法,那么你真的需要更深入地研究反射(reflect
包),它会更慢。上面提供的解决方案与 "hard-coded" 版本的性能大致相同。
完全反射
如果你希望它完全"generic",你可以使用反射来完成,然后你绝对不需要任何额外的方法。
不检查错误,这里是解决方案:
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
的值。在 Go Playground.
有嵌入和反射
通过将其名称指定为 string
来获取字段非常脆弱(例如考虑重命名/重构)。如果我们 "outsource" ID
字段和一个访问器方法到一个单独的 struct
,我们可以提高可维护性、安全性和反射的性能,我们将嵌入它,并捕获访问器通过界面:
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))
输出相同。在 Go Playground 上试试吧。请注意,在这种情况下,唯一的方法是 IDWrapper.GetID()
,不需要定义其他方法。