为了账号安全,请及时绑定邮箱和手机立即绑定

如何从 Go 1.18 中的单个方法返回两种不同的具体类型?

如何从 Go 1.18 中的单个方法返回两种不同的具体类型?

Go
catspeake 2023-01-03 10:02:05
假设我有这段代码:type Type1 struct {    Name string `json:"name,omitempty"`    Path string `json:"path"`    File string `json:"file"`    Tag  int    `json:"tag"`    Num  int    `json:"num"`}func LoadConfiguration(data []byte) (*Type1, error) {    config, err := loadConf1(data)    if err != nil {        return nil, err    }    confOther, err := loadConfOther1()    if err != nil {        return nil, err    }    // do something with confOther    fmt.Println("confOther", confOther)    if confOther.Tag == 0 {        config.Num = 5    }    // do something with config attributes of type1    if config.Tag == 0 {        config.Tag = 5    }    if config.Num == 0 {        config.Num = 4    }    return config, nil}func loadConf1(bytes []byte) (*Type1, error) {    config := &Type1{}    if err := json.Unmarshal(bytes, config); err != nil {        return nil, fmt.Errorf("cannot load config: %v", err)    }    return config, nil}func loadConfOther1() (*Type1, error) {    // return value of this specific type    flatconfig := &Type1{}    // read a file as []byte    // written as a fixed array to simplify this example    fileContent := []byte{10, 22, 33, 44, 55}    if err := json.Unmarshal(fileContent, flatconfig); err != nil {        return nil, fmt.Errorf("cannot read config %v", err)    }    return flatconfig, nil}唯一的公共功能是LoadConfiguration。它基于真实代码,用于读取 json 数据作为特定结构。如果某些东西看起来没用,那是因为我简化了原始代码。上面的代码没问题,但现在我想创建另一个名为“Type2”的结构类型,并重新使用相同的方法将数据读入 Type2 而无需复制和粘贴所有内容。type Type2 struct {    Name  string                  `json:"name,omitempty"`    Path  string                  `json:"path"`    Map   *map[string]interface{} `json:"map"`    Other string                  `json:"other"`}基本上,我希望能够调用LoadConfigurationType2。我可以接受调用特定方法,例如LoadConfiguration2,但我也不想复制和粘贴loadConf1和 loadConfOther1。有没有办法在 Go 1.18 中以惯用的方式做到这一点?
查看完整描述

1 回答

?
神不在的星期二

TA贡献1963条经验 获得超6个赞

实际上,您问题中显示的代码除了将类型传递给错误并设置错误格式外,什么也没做json.Unmarshal,因此您可以重写函数以使其表现得像它一样:


func LoadConfiguration(data []byte) (*Type1, error) {

    config := &Type1{}

    if err := loadConf(data, config); err != nil {

        return nil, err

    }

    // ...

}


// "magically" accepts any type

// you could actually get rid of the intermediate function altogether

func loadConf(bytes []byte, config any) error {

    if err := json.Unmarshal(bytes, config); err != nil {

        return fmt.Errorf("cannot load config: %v", err)

    }

    return nil

}

如果代码实际上做的不仅仅是将指针传递给json.Unmarshal,它可以从类型参数中获益。


type Configurations interface {

    Type1 | Type2

}


func loadConf[T Configurations](bytes []byte) (*T, error) {

    config := new(T)

    if err := json.Unmarshal(bytes, config); err != nil {

        return nil, fmt.Errorf("cannot load config: %v", err)

    }

    return config, nil

}


func loadConfOther[T Configurations]() (*T, error) {

    flatconfig := new(T)

    // ... code

    return flatconfig, nil

}

在这些情况下,您可以创建一个任一类型的新指针,new(T)然后json.Unmarshal负责将字节切片或文件的内容反序列化到其中——前提是 JSON 实际上可以解组到任一结构中。


顶层函数中的特定于类型的代码应该仍然不同,特别是因为您想要使用显式具体类型实例化通用函数。所以我建议保留LoadConfiguration1and LoadConfiguration2。


func LoadConfiguration1(data []byte) (*Type1, error) {

    config, err := loadConf[Type1](data)

    if err != nil {

        return nil, err

    }

    confOther, err := loadConfOther[Type1]()

    if err != nil {

        return nil, err

    }


    // ... type specific code


    return config, nil

}

但是,如果特定于类型的代码只是其中的一小部分,您可能可以为特定部分使用类型切换,尽管在您的情况下这似乎不是一个可行的选择。我看起来像:


func LoadConfiguration[T Configuration](data []byte) (*T, error) {

    config, err := loadConf[T](data)

    if err != nil {

        return nil, err

    }

    // let's pretend there's only one value of type parameter type

    // type-specific code

    switch t := config.(type) {

        case *Type1:

            // ... some *Type1 specific code

        case *Type2:

            // ... some *Type2 specific code

        default:

            // can't really happen because T is restricted to Configuration but helps catch errors if you extend the union and forget to add a corresponding case

            panic("invalid type")

    }


    return config, nil

}

最小的游乐场示例:https ://go.dev/play/p/-rhIgoxINTZ


查看完整回答
反对 回复 2023-01-03
  • 1 回答
  • 0 关注
  • 101 浏览
慕课专栏
更多

添加回答

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信