【问题标题】:Function that returns Slice of Custom Interface返回自定义接口切片的函数
【发布时间】:2020-08-14 20:42:26
【问题描述】:

首先,我想向您介绍一下,因为我觉得我缺少一些核心 Golang 概念。

在我的应用程序中,许多模型都有一个名为 GetByUserId 的方法。我创建了需要此方法的接口(UserCreatedEntity),因此我将能够为每种类型的记录创建控制器的 GetUserRecords 方法工厂,只需:

router.Handle("/ideas/mine",
        middlewares.AuthUser(controllers.GetMineFactory(&models.Idea{}))).Methods("POST")

router.Handle("/votes/mine",
        middlewares.AuthUser(controllers.GetMineFactory(&models.Vote{}))).Methods("POST")

router.Handle("/someNewType/mine",
        middlewares.AuthUser(controllers.GetMineFactory(&models.SomeNewType{}))).Methods("POST")

这就是我的界面的样子:

type UserCreatedEntity interface {
    GetByUserId(userId uint) []UserCreatedEntity
}

及实施:

func (idea *Idea) GetByUserId(userId uint) []UserCreatedEntity {
    ideas := []Idea{}
    GetDB().
        Table("ideas").
        /** Query removed to make code less confusing **/
        Scan(ideas)

    return ideas
}

显然,它不起作用(带有指针切片的版本也不起作用)。问题是——如果我只返回一条记录,这段代码就可以工作——就像那样(显然也改变了接口中的签名):

func (idea *Idea) GetByUserId(userId uint) UserCreatedEntity {
    idea := &Idea{}
    GetDB().
        Table("ideas").
        /** Query removed to make code less confusing **/
        First(idea)

    return idea
}

如何让它像切片一样工作?正如我所说,我怀疑我错过了一些重要的知识。如此深入的解释会很棒。

解决方案:

func (idea *Idea) GetByUserId(userId uint) []UserCreatedEntity {
    ideas := []*Idea{}
    GetDB().
        Table("ideas").
        Select("problems.name AS problem_name, ideas.id, ideas.problem_id, ideas.action_description, ideas.results_description, ideas.money_price, ideas.time_price, ideas.is_published").
        Joins("INNER JOIN problems ON ideas.problem_id = problems.id").
        Where("ideas.user_id = ?", userId).
        Scan(&ideas)

    uces := make([]UserCreatedEntity, len(ideas))
    for i, idea := range ideas {
        uces[i] = idea
    }
    return uces
}

【问题讨论】:

标签: go interface slice


【解决方案1】:

接口是动态的。涉及接口的复合类型不是。

UserCreatedEntity是一个接口,Idea满足接口,所以你可以从一个签名返回类型为UserCreatedEntity的函数返回一个Idea

[]UserCreatedEntityUserCreatedEntity 的一部分,而不是接口。唯一可以返回的类型是[]UserCreatedEntity[]Idea 是不同的类型(Idea 的切片)。你可以用Idea元素填充[]UserCreatedEntity,因为每个元素都是UserCreatedEntity类型,这又是一个接口,Idea在那里是允许的。

同样,func() UserCreatedEntity 是一种“返回 UserCreatedEntity 的函数”类型。您不能替换 func() Idea,因为那是不同的类型。但是您可以从func() UserCreatedEntity 返回Idea,因为IdeaUserCreatedEntity

如果您在此处未使用Scan(可能使用反射),则解决方法是将您的本地切片声明为[]UserCreatedEntity 而不是[]Idea。由于您使用的是Scan,因此您必须扫描到[]Idea,然后对其进行迭代以将所有元素复制到[]UserCreatedEntity 并返回。

【讨论】:

    【解决方案2】:

    在编程语言理论中,这称为方差,而it is not supported in Go。有关更多详细信息,请参阅this proposal

    具体来说,返回类型不是协变的。即使T 实现了IT 的切片也不会实现I 的切片。

    上面链接的 FAQ 条目提出了这种解决方法:

    需要将元素单独复制到目的地 片。此示例将 int 切片转换为 interface{} 切片:

    t := []int{1, 2, 3, 4}
    s := make([]interface{}, len(t))
    for i, v := range t {
        s[i] = v
    }
    

    尽管在您的情况下,正确的解决方案可能会有所不同。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-02-27
      • 2014-06-01
      • 2014-08-06
      • 2019-09-27
      • 2018-04-16
      • 2020-11-28
      • 1970-01-01
      相关资源
      最近更新 更多