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

时间:2017-02-22 19:37:42

标签: generics go struct slice

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

function addHand() {
  var htmlString;
  for(i = 0; i < hands[currentPlayersTurn].handArray.length; i++) {
    htmlString += "/  "+ hands[currentPlayersTurn].handArray[i].name +" ";
  }
  $("#playerHand").html(htmlString);
}

如果我在任何给定时间(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)都有这些结构的数组,我怎样才能编写一个从中提取ID的函数结构数组没有写3(或在我的情况下,10)单独的函数,如下:

[]C

3 个答案:

答案 0 :(得分:2)

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

您可能很想使用embedding,但我不确定是否有任何方法可以更轻松地完成此特定任务。嵌入感觉就像子类化,但它并没有给你多态性的力量。

Go中的多态性仅限于方法和接口,而不是字段,因此您无法在多个类中按名称访问给定字段。

您可以使用reflection按名称(或标记)查找和访问您感兴趣的字段,但会对此产生性能损失,这将使您的代码变得复杂且难以遵循。反射并不是真正意图替代多态或泛型。

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

type IDable interface {
    GetId() int64
}

为每个类创建一个GetId方法。 Full example

答案 1 :(得分:2)

使用切片本身的一般方法

如果定义一个通用接口来访问切片的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)))

输出(在Go Playground上尝试):

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

请注意,我们可以使用AListBList等类型的切片,我们不需要使用interface{}[]SomeIface。另请注意,我们也可以使用例如一个[]C,当它传递给GetIDs()时,我们使用了一个简单类型conversion

这很简单。如果你想要消除切片的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,我们可以提高可维护性,安全性和某种反射性能,并且我们通过接口:

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(),不需要定义其他方法。

答案 2 :(得分:0)

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