【问题标题】:Is it possible to pass a slice of a struct as an interface to a method?是否可以将结构的一部分作为接口传递给方法?
【发布时间】:2020-04-24 11:14:38
【问题描述】:

我在 Go 中编写了一个简单的 API,使用 github.com/gin-gonic/gin。它由数据库支持,我使用github.com/jinzhu/gorm 来查询和更新数据库。

我有许多端点几乎使用相同的代码从数据库中获取数据,因此我尝试创建一个适用于我所有数据库类型的单个 retrieve 方法。

例如,我有以下结构:

type Image struct {
  gorm.Model
  Name string
  Filename string
}

我的路由的API声明如下:

func (v1 *ApiV1) getImage(c *gin.Context) {
  var images []db.Image

  httpStatus := v1.retrieve("name", c.Param("name"), &images)

  c.JSON(
    httpStatus,
    images,
  )
}

可以看出,我正在尝试将指针传递给我希望查询填充的结构。由于数据库中的不同类型需要几个不同的端点,我想让retrieve 方法可重用,所以我用这个签名创建了它:

func (v1 *ApiV1) retrieve(fieldName string, name string, returnObject *[]interface{}) int {

    // create var to hold the http status
    var httpStatus int = http.StatusOK

    // determine if a name has been passed
    if name == "" {
        v1.app.DB.Find(returnObject)
    } else {

        // look for the name in the database
        v1.app.DB.Where(fieldName+" = ?", name).First(returnObject)

        // if the name cannot be found then set the status to 404
        if len(*returnObject) == 0 {
            httpStatus = http.StatusNotFound
        }
    }

    return httpStatus
}

为了尝试使其“通用”,我使用*[]interface{} 作为returnObject 的类型。 (这个名字有点用词不当,因为它是指针,但它很容易理解某些东西会被更新)。

但是,当我运行它时,我收到以下错误:

cannot use &images (type *[]db.Image) as type *[]interface {} in argument to v1.retrieve

我完全理解 Go 是一种严格类型化的语言,但由于我希望能够重用我的 retrieve 函数,我需要能够传入指向 Gorm 可以填充的切片(任何类型)的指针当查询运行时。

我目前正在从一个版本中重构它,该版本确实具有用于检索每个 API 端点的单独函数,但显然这是低效的并且使得执行任何重构变得更加困难。

那么我要问的可能吗?

【问题讨论】:

  • 将 returnObject 类型更改为interface{}。至于类型错误[]T is not []I 更是如此*[]T is not *[]I。这两种类型都没有相同的基础类型,因此您甚至无法使用转换表达式T(v) 将一种转换为另一种。 golang.org/doc/faq#convert_slice_of_interface
  • 请注意,如果T 实现I,则可以将T 用作I,但这并不意味着您也可以将[]T 用作[]I因此您不能将[]interface{} 用作any 切片类型的包罗万象,那是行不通的。
  • @mkopriva 谢谢。这让我意识到我做的不对。我已经修改了我的代码并发布了答案。

标签: go go-gorm


【解决方案1】:

感谢@mkopriva(并在查看代码的过程中休息一下)我能够弄清楚如何获得可重复使用的检索功能。

正如 cmets 中所述,我试图将 []T[]I 进行比较,这是行不通的。所以我通过代码重构使用 GORM 链中的表名,这意味着结果现在可以是一个接口,并将其作为返回项而不是指针传递回来。

所以现在我的retrieve 方法看起来像:

func (v1 *ApiV1) retrieve(tableName string, fieldName string, name string) ([]interface{}, int) {

    // create var to hold the http status
    var httpStatus int = http.StatusOK
    var result []interface{}

    // determine if a name has been passed
    if name == "" {
        v1.app.DB.Find(result)
    } else {

        // look for the name in the database
        v1.app.DB.Table(tableName).Where(fieldName+" = ?", name).Order("id DESC").First(&result)

        // if the name cannot be found then set the status to 404
        if len(result) == 0 {
            httpStatus = http.StatusNotFound
        }
    }

    return result, httpStatus
}

这样做的缺点是我必须传入要查询的数据库表的名称,从结果对象中推断出来的。因为我不太可能更换桌子,所以这并没有太大的不便。

我还必须在链中添加一个ORDER 以解决使用 GORM 时的 MSSQL 问题。

所以现在 API 函数看起来像:

func (v1 *ApiV1) getImage(c *gin.Context) {

    // call method to retrieve the items from the database
    images, httpStatus := v1.retrieve("images", "name", c.Param("name"))

    c.JSON(
        httpStatus,
        images,
    )
}

【讨论】:

  • 你试过我最初的建议了吗?即将returnObject 类型更改为interface{}interface{} ,最终,gorm 方法接受什么作为参数,包括.First.Find,对吧?那么为什么不将这种类型的值从你的函数传递给 gorm?当你将interface{} 从一个函数传递到另一个函数时,它的底层动态值不会随机改变,如果你传递给retrieve,它仍然是*[]db.Image!所以请尝试play.golang.com/p/TjyBTIP1JsL
  • 要(也许)进一步澄清这一点:var v interface{} = &[]db.Image{} is OK 并且 will 编译。另一方面,var v []interface{} = []db.Image{} ok,不会编译,var v *[]interface{} = &[]db.Image{}!
  • 在第一个示例中,v 是一个接口类型,您可以将其值设置为实现该接口的任何内容,但是在第二个示例中,v 是一个 slice类型,无论切片元素的类型是什么,在设置v 时都使用完全相同的类型,指针类型也是如此。仅仅因为类型是指向接口或接口切片的指针,并不意味着您可以将其设置为指向任何实现该接口或任何实现该接口的切片的指针的值。
猜你喜欢
  • 2012-02-09
  • 2012-03-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-11-20
  • 1970-01-01
  • 2015-05-02
相关资源
最近更新 更多