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

假设我有这段代码:


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 中以惯用的方式做到这一点?


catspeake
浏览 110回答 1
1回答

神不在的星期二

实际上,您问题中显示的代码除了将类型传递给错误并设置错误格式外,什么也没做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 altogetherfunc 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
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Go