在任何具有底层字段的结构体数组上调用方法。

huangapple go评论78阅读模式
英文:

Call method on any array of structs that have underlying field

问题

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

type A struct {
    ID int64
    ... 其他A特定的字段
}

type B struct {
    ID int64
    ... 其他B特定的字段
}

type C struct {
    ID int64
    ... 其他C特定的字段
}

如果我在任何给定时间都有这些结构体的数组([]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 { ... }
英文:

Let's say I have a bunch of structs (around 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
}

If I have an array of these structs at any given time (either []A, []B, or []C), how can I write a single function that pulls the IDs from the array of structs without writing 3 (or in my case, 10) separate functions like this:

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

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

答案1

得分: 2

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

你可能会尝试使用嵌入(embedding),但我不确定是否有任何方法可以使这个特定的任务变得更容易。嵌入感觉像是子类化,但它并不能给你多态的能力。

在Go语言中,多态性仅限于方法和接口,而不是字段,因此你无法通过名称在多个类之间访问给定的字段。

你可以使用反射(reflection)通过名称(或标签)查找和访问你感兴趣的字段,但这会带来性能损失,并且会使你的代码变得复杂和难以理解。反射并不真正意味着要替代多态性或泛型。

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

type IDable interface {
    GetId() int64
}

然后为你的每个类创建一个GetId方法。完整示例

英文:

As far as I know, there is no easy way.

You might be tempted to use embedding, but I'm not sure there's any way to make this particular task any easier. Embedding feels like subclassing but it doesn't give you the power of polymorphism.

Polymorphism in Go is limited to methods and interfaces, not fields, so you can't access a given field by name across multiple classes.

You could use reflection to find and access the field you are interested in by name (or tag), but there are performance penalties for that and it will make your code complex and hard to follow. Reflection is not really intended to be a substitute for Polymorphism or generics.

I think your best solution is to use the polymorphism that Go does give you, and create an interface:

type IDable interface {
	GetId() int64
}

and make a GetId method for each of your classes. Full example.

答案2

得分: 2

使用切片本身的通用方法

如果你定义一个通用接口来访问切片中第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 }

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]

注意,我们可以使用AListBList等类型的切片,而不需要使用interface{}[]SomeIface。还要注意,我们也可以使用[]C,并在将其传递给GetIDs()时使用简单的类型转换。

这是最简单的方法。如果你想要连切片的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上尝试一下。

使用嵌入和反射

通过将字段名作为字符串来获取字段是相当脆弱的(例如,考虑重命名/重构)。如果我们将ID字段和一个访问器方法外包到一个单独的结构体中,并将其嵌入到其他结构体中,同时通过接口捕获访问器,可以提高可维护性、安全性,并在一定程度上提高反射的性能:

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)都会自动实现HasID接口,并自动提升GetID()方法。我们可以在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(),不需要定义其他方法。

英文:

With general method on the slice itself

You can make it a little simpler if you define a general interface to access the ID of the i<sup>th</sup> element of a slice:

type HasIDs interface {
	GetID(i int) int64
}

And you provide implementation for these:

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 }

And then one GetID() function is enough:

func GetIDs(s HasIDs) (ids []int64) {
	ids = make([]int64, reflect.ValueOf(s).Len())
	for i := range ids {
		ids[i] = s.GetID(i)
	}
	return
}

Note: the length of the slice may be a parameter to GetIDs(), or it may be part of the HasIDs interface. Both are more complex than the tiny reflection call to get the length of the slice, so bear with me on this.

Using it:

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)))

Output (try it on the Go Playground):

[1 2]
[3 4]
[5 6]

Note that we were able to use slices of type AList, BList etc, we did not need to use interface{} or []SomeIface. Also note that we could also use e.g. a []C, and when passing it to GetIDs(), we used a simple type conversion.

This is as simple as it can get. If you want to eliminate even the GetID() methods of the slices, then you really need to dig deeper into reflection (reflect package), and it will be slower. The presented solution above performs roughly the same as the "hard-coded" version.

With reflection completely

If you want it to be completely "generic", you may do it using reflection, and then you need absolutely no extra methods on anything.

Without checking for errors, here's the solution:

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(&quot;ID&quot;).Int()
	}
	return
}

Testing and output is (almost) the same. Note that since here parameter type of GetIDs() is interface{}, you don't need to convert to CList to pass a value of type []C. Try it on the Go Playground.

With embedding and reflection

Getting a field by specifying its name as a string is quite fragile (think of rename / refactoring for example). We can improve maintainability, safety, and somewhat the reflection's performance if we "outsource" the ID field and an accessor method to a separate struct, which we'll embed, and we capture the accessor by an interface:

type IDWrapper struct {
	ID int64
}

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

type HasID interface {
	GetID() int64
}

And the types all embed IDWrapper:

type A struct {
	IDWrapper
}

type B struct {
	IDWrapper
}

type C struct {
	IDWrapper
}

By embedding, all the embedder types (A, B, C) will have the GetID() method promoted and thus they all automatically implement HasID. We can take advantage of this in the GetIDs() function:

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
}

Testing it:

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))

Output is the same. Try it on the Go Playground. Note that in this case the only method is IDWrapper.GetID(), no other methods needed to be defined.

答案3

得分: 0

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

英文:

Generic methods require the use of interfaces and reflection.

huangapple
  • 本文由 发表于 2017年2月23日 03:37:42
  • 转载请务必保留本文链接:https://go.coder-hub.com/42400723.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定