Go泛型和接口

ktca8awb  于 2023-06-27  发布在  Go
关注(0)|答案(1)|浏览(107)

我很难理解泛型和接口是如何相互作用的,也很难知道哪个实现将从定义为返回接口的函数返回。
我怎样才能让下面的代码以我想要的方式工作。

package main

import (
    "encoding/json"
    "fmt"
    "reflect"
)

type IntegrationConfig[T interface{}] interface {
}

type NamedJsonSettings[T interface{}] json.RawMessage

func (m *NamedJsonSettings[T]) GetConfig() (T, error) {
    conf := new(T)
    err := json.Unmarshal(*m, conf)
    return *conf, err
}

type NamedConfig struct {
    Username string `json:"username"`
    Password string `json:"password"`
}

func getConfig[T interface{}]() IntegrationConfig[T] {
    serverConfigStr := `{
    "username": "myusername",
    "password": "supersecret"
}`
    sc := NamedJsonSettings[T](serverConfigStr)
    c, err := sc.GetConfig()
    if err != nil {
        fmt.Printf("ERROR: %s", err)
    }
    return c
}

func main() {

    c := getConfig[NamedConfig]()

    fmt.Printf("%+v\n", c)
    fmt.Printf("%s\n", c.Username)
    fmt.Printf("%s\n", c.Password)
    fmt.Printf("%s\n",reflect.TypeOf(c).String())
}

运行此命令将生成以下错误:

./scratch_1.go:43:23: c.Username undefined (type IntegrationConfig[NamedConfig] has no field or method Username)
./scratch_1.go:44:23: c.Password undefined (type IntegrationConfig[NamedConfig] has no field or method Password)

如果我删除这两行,当我在控制台中运行它时,我会看到以下内容,这是我所期望的:

{Username:myusername Password:supersecret}
main.NamedConfig

我哪里做错了?另外,如果您能提供一些很好的资源链接,帮助您更好地理解泛型,特别是在以这种方式使用接口的情况下,我们将不胜感激。

9jyewag0

9jyewag01#

函数getConfig[NamedConfig]()返回IntegrationConfig[NamedConfig],我们可以看到它的定义:

type IntegrationConfig[T interface{}] interface {
}

你可以看到这只是interface{}(a.k.a. * 任何值都可以转换为any,因此实际返回的对象是否具有字段是无关紧要的。我们可以像这样构造一个类似的测试用例:

package main

import "fmt"

type t struct {
    field int
}

func a() any {
    return t{field: 12}
}

func main() {
    value := a()
    fmt.Printf("value.field = %d\n", value.field)
}

即使value中存储的实际有一个名为field的字段,类型any也不会公开该字段。
如果要访问字段或方法,这些字段/方法必须按变量的类型公开,而字段/方法是否按值的类型公开并不重要。(这与Python或JavaScript不同,后者允许您访问对象上的任何字段或方法,只要该字段或方法在运行时存在。

如何修复

首先,使用any而不是interface{}是正常的。它们的意思相同,但any更典型。
各种中间类型不是必需的。您可以直接从泛型返回T。由于T是泛型中的普通类型,因此不必使用new(T)
在这段代码中,变量c的类型为NamedConfig,而不是IntegrationConfig[NamedConfig](即any)。因为它的类型是NamedConfig--因为 * 变量本身 * 是该类型而不仅仅是值--所以您可以访问这些字段。

package main

import (
    "encoding/json"
    "fmt"
)

type NamedConfig struct {
    Username string `json:"username"`
    Password string `json:"password"`
}

func getConfig[T any]() T {
    serverConfigStr := `{
    "username": "myusername",
    "password": "supersecret"
}`
    var value T
    if err := json.Unmarshal([]byte(serverConfigStr), &value); err != nil {
        fmt.Printf("ERROR: %v\n", err)
    }
    return value
}

func main() {
    // Here, c has type NamedConfig.
    c := getConfig[NamedConfig]()

    fmt.Printf("%+v\n", c)
    fmt.Printf("%s\n", c.Username)
    fmt.Printf("%s\n", c.Password)
}

相关问题