【问题标题】:slice of struct != slice of interface it implements?结构切片!=它实现的接口切片?
【发布时间】:2012-10-11 06:34:31
【问题描述】:

我有一个接口Model,由struct Person实现。

为了获取模型实例,我有以下辅助函数:

func newModel(c string) Model {
    switch c {
    case "person":
        return newPerson()
    }
    return nil
}

func newPerson() *Person {
    return &Person{}
}

上述方法允许我返回一个正确类型的 Person 实例(以后可以使用相同的方法轻松添加新模型)。

当我尝试执行类似的操作来返回一个模型切片时,我收到了一个错误。代码:

func newModels(c string) []Model {
    switch c {
    case "person":
        return newPersons()
    }
    return nil
}

func newPersons() *[]Person {
    var models []Person
    return &models
}

Go 抱怨:cannot use newPersons() (type []Person) as type []Model in return argument

我的目标是返回请求的任何模型类型的切片(无论是[]Person[]FutureModel[]Terminator2000,w/e)。我缺少什么,如何正确实施这样的解决方案?

【问题讨论】:

  • 切片不同于 Go 中的数组。既然你真的在谈论切片,我编辑了你的帖子以反映这一点。
  • 斯蒂芬,谢谢,感激 :-)
  • @JonL。你有没有想过这个?我正在尝试做同样的事情,这样我就不必为我的/api/{collection} 重复大量代码。除了需要读入切片的索引函数之外,我让它适用于所有内容。
  • @DerekPerkins,我不记得我最终在这里做了什么,而且我已经有一段时间没有玩围棋了。抱歉,我无法提供更多帮助。
  • 逆变(或者是协方差?)又来了!

标签: go


【解决方案1】:

类型 T 和 []T 是不同的类型,它们的方法也不同,即使满足相同的接口也是如此。 IOW,满足 Model 的每个类型都必须自己实现 Model 的所有方法——方法接收器只能是一种特定类型。

【讨论】:

    【解决方案2】:

    这和我刚刚回答的一个问题很相似:https://stackoverflow.com/a/12990540/727643

    简短的回答是你是对的。结构的切片不等于结构实现的接口的切片。

    []Person[]Model 具有不同的内存布局。这是因为它们是切片的类型具有不同的内存布局。 Model 是一个接口值,这意味着在内存中它的大小是两个字。一个字用于类型信息,另一个用于数据。 Person 是一个结构,其大小取决于它包含的字段。为了从[]Person 转换为[]Model,您需要遍历数组并对每个元素进行类型转换。

    由于这个转换是一个 O(n) 操作并且会导致创建一个新的切片,Go 拒绝隐式地这样做。您可以使用以下代码显式执行此操作。

    models := make([]Model, len(persons))
    for i, v := range persons {
        models[i] = Model(v)
    }
    return models
    

    作为dskinner pointed out,您很可能需要一个指针切片,而不是指向切片的指针。通常不需要指向切片的指针。

    *[]Person        // pointer to slice
    []*Person        // slice of pointers
    

    【讨论】:

    • 当时没有意识到,但我的回答并没有直接解决提出的问题。这个答案解决了它。
    • 非常好的答案,尤其是对每种类型的记忆单词数的解释
    • 那么您如何以抽象、通用的方式编写内容呢?想象一下,您有 100k 个来自数据库查询的结果,您必须遍历所有结果并创建每个数据集的副本。这意味着你不能在 Go 中使用通用接口
    • 你的回答是正确的,但解释让我觉得如果我有一个接口A 实现我想要的接口B 我将能够使用As 的一部分作为Bs 的一部分(毕竟,内存结构是相同的,因为它们都是接口)。我很失望地发现我不能:(
    • 接口 A 和接口 B 有不同的 vtable。所以它在内存中的布局并不完全相同。如果它处理接口 A 的方式与处理接口 B 的方式相同,它可能会尝试执行错误的函数。您可以使用 unsafe.Pointer 自己尝试一下。这将是一个有趣的实验!
    【解决方案3】:

    也许这是您的返回类型*[]Person 的问题,它实际上应该是[]*Person,以便引用切片的每个索引都是对Person 的引用,而切片[] 是本身就是对数组的引用。

    查看以下示例:

    package main
    
    import (
        "fmt"
    )
    
    type Model interface {
        Name() string
    }
    
    type Person struct {}
    
    func (p *Person) Name() string {
        return "Me"
    }
    
    func NewPersons() (models []*Person) {
        return models
    }
    
    func main() {
        var p Model
        p = new(Person)
        fmt.Println(p.Name())
    
        arr := NewPersons()
        arr = append(arr, new(Person))
        fmt.Println(arr[0].Name())
    }
    

    【讨论】:

    • 感谢您的回复。虽然(如您所述)它并没有直接解决手头的问题,但它仍然是一个值得赞赏的例子:-)
    【解决方案4】:

    由于斯蒂芬已经回答了这个问题并且你是初学者,我强调提供建议。

    使用 go 接口的更好方法是不让构造函数返回 您可能习惯于使用其他语言(如 java)的界面,但具有 每个对象独立的构造函数,因为它们隐式实现接口。

    代替

    newModel(type string) Model { ... }
    

    你应该这样做

    newPerson() *Person { ... }
    newPolitician() *Politician { ... }
    

    PersonPolitician 都实现了Model 的方法。 你仍然可以在Model 的任何地方使用PersonPolitician 被接受,但你也可以实现其他接口。

    使用您的方法,您将被限制为 Model,直到您手动转换为 另一种接口类型。

    假设我有一个实现Walk() 方法的Person 和一个实现ShowOff() 方法的Model,则以下内容无法直接工作:

    newModel("person").ShowOff()
    newModel("person").Walk() // Does not compile, Model has no method Walk
    

    但是这样会:

    newPerson().ShowOff()
    newPerson().Walk()
    

    【讨论】:

    • 你是对的 :-( 我的方法是尝试通过 ReST API 一般地提供对模型的访问。所以对/api/{collection} 的请求将与请求的集合动态交互。你能建议无需跨多个函数显式测试请求的集合的替代解决方案?我想我正在寻找一种在不丢失类型知识的情况下指定通用返回类型的方法。
    • 始终保留类型的知识,即使您返回interface{},问题是您需要执行运行时类型断言而不是编译器类型检查。没有运行时断言的name -> object 的通用解决方案只能使用 go 不支持的泛型。因此,如果您这样做,您必须忍受反思或解决方案的缺点。
    • 重点是 []Model != []Person 甚至 []Model != []Politician。这就是问题所在。
    • @dalu 正确。我也在我的答案的顶部提到了这一点:这个答案提供了关于键入和 go 接口约定的补充信息,OP 似乎不知道但接受的答案没有解决。
    【解决方案5】:

    正如其他人已经回答的那样,[]T 是一种独特的类型。我想补充一点,可以使用一个简单的实用程序对它们进行通用转换。

    import "reflect"
    
    // Convert a slice or array of a specific type to array of interface{}
    func ToIntf(s interface{}) []interface{} {
        v := reflect.ValueOf(s)
        // There is no need to check, we want to panic if it's not slice or array
        intf := make([]interface{}, v.Len())
        for i := 0; i < v.Len(); i++ {
            intf[i] = v.Index(i).Interface()
        }
        return intf
    }
    

    现在,你可以这样使用它:

    ToIntf([]int{1,2,3})
    

    【讨论】:

      【解决方案6】:

      即使 Go 的实现允许这样做,不幸的是它还是不合理的:您不能将 []Person 分配给 []Model 类型的变量,因为 []Model 具有不同的功能。例如,假设我们还有Animal,它实现了Model

      var people []Person = ...
      var models []Model = people // not allowed in real Go
      models[0] = Animal{..} // ???
      var person Person = people[0] // !!!
      

      如果我们允许第 2 行,那么第 3 行也应该可以工作,因为models 可以完美地存储Animal。第 4 行应该仍然有效,因为 people 存储 Persons。但是我们最终会得到一个Person 类型的变量,其中包含一个Animal

      Java 实际上允许等效于第 2 行,并且它被广泛认为是一个错误。 (错误在运行时被捕获;第 3 行将抛出 ArrayStoreException。)

      【讨论】:

        猜你喜欢
        • 2019-01-05
        • 2012-02-25
        • 2020-02-03
        • 2019-10-19
        • 1970-01-01
        • 1970-01-01
        • 2013-12-08
        • 2015-02-21
        相关资源
        最近更新 更多