假设我有一堆结构(大约 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 { ... }
最佳答案
使用 slice 本身的通用方法
如果您定义一个通用接口(interface)来访问 slice 的第 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
}
注意: slice 的长度可能是GetIDs()
的参数,也可能是HasIDs
接口(interface)的一部分。两者都比获取 slice 长度的微小反射调用更复杂,所以请耐心等待。
使用它:
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
等类型的 slice ,我们不需要使用 interface{}
或 []SomeIface
。另请注意,我们也可以使用例如[]C
,当将它传递给 GetIDs()
时,我们使用了一个简单的类型 conversion .
这很简单。如果你甚至想消除 slice 的 GetID()
方法,那么你真的需要更深入地研究反射(reflect
包),它会更慢。上面提供的解决方案与“硬编码”版本的性能大致相同。
完全反射
如果你想让它完全“通用”,你可以使用反射来实现,然后你绝对不需要任何额外的方法。
在不检查错误的情况下,这是解决方案:
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
来获取字段非常脆弱(例如,考虑重命名/重构)。如果我们将 ID
字段和访问器方法“外包”到我们将嵌入的单独的 struct
中,我们可以提高可维护性、安全性和反射的性能,并且我们通过接口(interface)捕获访问器:
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()
,不需要定义其他方法。
关于go - 在具有基础字段的任何结构数组上调用方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42400723/