【问题标题】:How to unite two different struct using interface?如何使用接口联合两个不同的结构?
【发布时间】:2017-02-05 02:50:45
【问题描述】:

我有以下代码:

package main

import (
    "log"
)

type Data struct {
    Id int
    Name string
}

type DataError struct {
    Message string
    ErrorCode string
}

func main() {
    response := Data{Id: 100, Name: `Name`}
    if true {
        response = DataError{Message: `message`, ErrorCode: `code`}
    }
    log.Println(response)
}

此代码返回一个错误:

./start.go:20: 不能使用 DataError 文字(类型 DataError)作为类型 赋值中的数据

似乎我无法分配给 response 不同类型的 var 数据(在我的情况下为 DataError)。我听说可能的解决方案是通过接口联合 DataDataError 结构。或者也许还有其他更好的解决方案?

请指点我如何解决这个问题?

谢谢

【问题讨论】:

  • 不,您不能将不同的类型分配给同一个变量。您可以使用空的interface{} (play.golang.org/p/h5vPe3Et1O),但这不是一个很好的解决方案。在 Go 中,您通常使用单独的错误值。你看过任何介绍材料吗?
  • @JimB,是的,但是界面对我来说仍然不够清晰 :)
  • 这两种类型没有必要合并没有问题。
  • @tsg,正如沃尔克所说,没有必要将这些组合起来,您可以使用单独的值。由于这两种类型都没有任何方法,因此满足相同接口的唯一方法集将是空的interface{}

标签: go struct interface go-interface


【解决方案1】:

您不能将两种不可“分配”的不同类型分配给同一个变量...除非您使用特定的接口签名或空接口。

https://golang.org/ref/spec#Assignability

该代码将编译:

func main() {
    var response interface{} // empty interface AKA Object AKA void pointer
    response = Data{Id: 100, Name: `Name`}
    if true {
        response = DataError{Message: `message`, ErrorCode: `code`}
    }
    log.Println(response)
}

因为每种类型都实现了空接口,但只有在没有其他选项的情况下才需要这样做。

如果 2 种类型共享某些方法使用特定接口,例如(伪代码):

type Responder interface {
    Respond() string
}

type Data struct { /* code */
}

func (d Data) Respond() string { return "" }

type DataError struct { /* code */
}

func (d DataError) Respond() string { return "" }

func main() {

    var response Responder // declared as interface
    response = Data{}
    response = DataError{}
    fmt.Println(response)

}

当您怀疑快速浏览 go 规范是否有用时,它是唯一的权威,与大多数规范相比,它写得非常好。在大多数情况下,规则非常清晰,这就是围棋的优势。

【讨论】:

  • 感谢您的快速回复。这对我来说很有趣:)
【解决方案2】:

看起来您正在尝试创建一个联合类型(ML 语言家族称之为“枚举”)。我知道这有两种模式:

0。基本错误处理 (playground)

我怀疑您所做的只是基本的错误处理。在 Go 中,我们使用多个返回值并检查结果。这几乎肯定是您想要做的:

package main

import (
    "fmt"
    "log"
)

type Data struct {
    ID   int
    Name string
}

type DataError struct {
    Message   string
    ErrorCode string
}

// Implement the `error` interface. `error` is an interface with
// a single `Error() string` method
func (err DataError) Error() string {
    return fmt.Sprintf("%s: %s", err.ErrorCode, err.Message)
}

func SomeFunction(returnData bool) (Data, error) {
    if returnData {
        return Data{ID: 42, Name: "Answer"}, nil
    }
    return Data{}, DataError{
        Message:   "A thing happened",
        ErrorCode: "Oops!",
    }
}

func main() {
    // this bool argument controls whether or not an error is returned
    data, err := SomeFunction(false)
    if err != nil {
        log.Fatalln(err)
    }
    fmt.Println(data)
}

1.接口 (playground)

同样,如果您的选项是好数据和错误,您可能应该使用第一种情况(坚持成语/惯例),但其他时候您可能有多个“好数据”选项。我们可以使用接口来解决这个问题。在这里,我们添加了一个虚拟方法来告诉编译器将可以实现此接口的可能类型限制为具有 IsResult() 方法的那些类型。这样做的最大缺点是,将内容粘贴到接口中会导致分配,这在紧密循环中可能是有害的。这种模式并不常见。

package main

import "fmt"

type Result interface {
    // a dummy method to limit the possible types that can
    // satisfy this interface
    IsResult()
}

type Data struct {
    ID   int
    Name string
}

func (d Data) IsResult() {}

type DataError struct {
    Message   string
    ErrorCode string
}

func (err DataError) IsResult() {}

func SomeFunction(isGoodData bool) Result {
    if isGoodData {
        return Data{ID: 42, Name: "answer"}
    }
    return DataError{Message: "A thing happened", ErrorCode: "Oops!"}
}

func main() {
    fmt.Println(SomeFunction(true))
}

2。标记联盟 (playground)

这种情况与前一种情况类似,除了不使用接口,我们使用带有标记的结构,该标记告诉我们结构包含哪种类型的数据(这类似于 C 中的标记联合,除了结构的大小是其潜在类型的总和,而不是其最大潜在类型的大小)。虽然这会占用更多空间,但它可以很容易地被堆栈分配,从而使其对紧密循环友好(我使用这种技术将分配从 O(n) 减少到 O(1))。在这种情况下,我们的标签是 bool,因为我们只有两种可能的类型(Data 和 DataError),但您也可以使用类似 C 的枚举。

package main

import (
    "fmt"
)

type Data struct {
    ID   int
    Name string
}

type DataError struct {
    Message   string
    ErrorCode string
}

type Result struct {
    IsGoodData bool
    Data       Data
    Error      DataError
}

// Implements the `fmt.Stringer` interface; this is automatically
// detected and invoked by fmt.Println() and friends
func (r Result) String() string {
    if r.IsGoodData {
        return fmt.Sprint(r.Data)
    }
    return fmt.Sprint(r.Error)
}

func SomeFunction(isGoodData bool) Result {
    if isGoodData {
        return Result{
            IsGoodData: true,
            Data:       Data{ID: 42, Name: "Answer"},
        }
    }
    return Result{
        IsGoodData: false,
        Error: DataError{
            Message:   "A thing happened",
            ErrorCode: "Oops!",
        },
    }
}

func main() {
    // this bool argument controls whether or not an error is returned
    fmt.Println(SomeFunction(true))
}

【讨论】:

  • 这正是我所需要的!多谢!非常详细的答案:)
  • @tsg 乐于助人。 :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-11-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-08-24
  • 2018-07-22
相关资源
最近更新 更多