How to unite two different struct using interface?

huangapple go评论96阅读模式
英文:

How to unite two different struct using interface?

问题

我有以下代码:

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: cannot use DataError literal (type DataError) as type
Data in assignment

看起来我不能将不同类型(在我的情况下是DataError)的数据分配给response变量。我听说可能的解决方案是通过接口将DataDataError结构体合并。或者也许还有其他更好的解决方案?

你能告诉我如何解决这个问题吗?

谢谢

英文:

I have the following code:

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)
}

This code returns me an error:

> ./start.go:20: cannot use DataError literal (type DataError) as type
> Data in assignment

Seems to be that I could not assign to response var data with different type (in my case DataError). I heard that possible solution could be to unite Data and DataError structs via interface. Or maybe there is another better solution?

Could you please point me how to resolve this problem?

Thanks

答案1

得分: 7

看起来你正在尝试创建一个联合类型(ML语言系列中称为"enum"的类型)。我知道几种解决这个问题的模式:

0. 基本错误处理(playground

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

package main

import (
	"fmt"
	"log"
)

type Data struct {
	ID   int
	Name string
}

type DataError struct {
	Message   string
	ErrorCode string
}

// 实现`error`接口。`error`是一个具有单个`Error() string`方法的接口
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() {
	// 这个布尔参数控制是否返回错误
	data, err := SomeFunction(false)
	if err != nil {
		log.Fatalln(err)
	}
	fmt.Println(data)
}

1. 接口(playground

同样,如果你的选项是好数据和错误,你应该使用第一种情况(坚持惯例),但有时你可能有多个“好数据”选项。我们可以使用接口来解决这个问题。在这里,我们添加了一个虚拟方法来告诉编译器将可以实现这个接口的可能类型限制为具有IsResult()方法的类型。这种模式的最大缺点是将东西放入接口可能会产生分配,这在紧密循环中可能会有害。这种模式并不是非常常见。

package main

import "fmt"

type Result 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))。在这种情况下,我们的标签是一个布尔值,因为我们只有两种可能的类型(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
}

// 实现`fmt.Stringer`接口;这会被fmt.Println()和相关函数自动检测和调用
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() {
	// 这个布尔参数控制是否返回错误
	fmt.Println(SomeFunction(true))
}

希望这些代码能帮到你!

英文:

It looks like you're trying to make a union type (what the ML-family of languages calls "enum"). I know of a couple of patterns for this:

0. Basic error handling (playground)

I suspect what you're doing is just basic error handling. In Go, we use multiple return values and check the result. This is almost certainly what you want to do:

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. Interfaces (playground)

Again, if your options are good-data and error, you should probably use the first case (stick with the idiom/convention), but other times you might have multiple "good data" options. We can use interfaces to solve this problem. Here we're adding a dummy method to tell the compiler to constrain the possible types that can implement this interface to those that have an IsResult() method. The biggest downside to this is that sticking things into an interface can incur an allocation, which can be detrimental in a tight loop. This pattern isn't terribly common.

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. Tagged Union (playground)

This case is similar to the previous case, except instead of using an interface, we're using a struct with a tag that tells us which type of data the struct contains (this is similar to a tagged union in C, except the size of the struct is the sum of its potential types instead of the size of its largest potential type). While this takes up more space, it can easily be stack-allocated, thus making it tight-loop friendly (I've used this technique to reduce allocs from O(n) to O(1)). In this case, our tag is a bool because we only have two possible types (Data and DataError), but you could also use a C-like enum.

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))
}

答案2

得分: 1

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

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

以下代码可以编译通过:

func main() {
    var response interface{} // 空接口,也称为Object或void指针
    response = Data{Id: 100, Name: `Name`}
    if true {
        response = DataError{Message: `message`, ErrorCode: `code`}
    }
    log.Println(response)
}

因为每个类型都实现了空接口,但你只想在没有其他选项时才这样做。

如果两个类型共享一些方法,可以使用特定的接口,例如(伪代码):

type Responder interface {
    Respond() string
}

type Data struct { /* 代码 */ }

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

type DataError struct { /* 代码 */ }

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

func main() {

    var response Responder // 声明为接口
    response = Data{}
    response = DataError{}
    fmt.Println(response)

}

当你有疑问时,快速查阅Go规范是有用的,它是唯一的权威,并且相对于大多数规范来说写得非常清晰。在大多数情况下,规则是明确的,这是Go的优势。

英文:

You can't assign 2 different types that are not "assignable" to the same variable ... unless you use a specific interface signature or empty interface.

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

that code would compile :

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)
}

since every type implements empty interface, but you want to do that only if there is no other options.

if 2 types share some methods use a specific interface, for instance (pseudo-code) :

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)

}

Whenever you have doubts a quick scan of the go spec is useful, it is the only authority and pretty well written compared to most specs out there. For the most part the rules are crystal clear, and that's a strength of Go.

huangapple
  • 本文由 发表于 2016年9月27日 20:52:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/39725223.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定