go - 在具有基础字段的任何结构数组上调用方法

标签 go struct slice

假设我有一堆结构(大约 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 的第 ith 个元素的 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]

请注意,我们可以使用 AListBList 等类型的 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
}

通过嵌入,所有嵌入器类型(ABC)都将具有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/

相关文章:

pointers - 空结构 slice 的地址

go - text/template.Templates 和 html/template.Templates 之间的区别

go - 在不创建实例的情况下打印结构类型

go - 在 Go 中读取命名管道时 100% CPU 使用率

go - 在 Golang 中,函数是否有可能在出错时进行裸返回?

c - 看似不可能输出链表

object - 类型断言非简约

c++ - Arduino/C++ : Access specific header file of structs from library

python - 如何使用索引有效地获取张量中每一行的值?

python - 如何将小型二维阵列添加到大型阵列?