Как объединить две разные структуры с помощью интерфейса?
У меня есть следующий код:
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
). Я слышал, что возможным решением может быть объединение Data
а также DataError
структурируется через интерфейс. Или, может быть, есть другое лучшее решение?
Не могли бы вы указать мне, как решить эту проблему?
Спасибо
2 ответа
Похоже, вы пытаетесь создать союзный тип (то, что в ML-семействе языков называется "enum"). Я знаю пару шаблонов для этого:
0. Базовая обработка ошибок ( детская площадка)
Я подозреваю, что вы делаете просто базовую обработку ошибок. В 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. Интерфейсы ( детская площадка)
Опять же, если ваши варианты - "хорошие данные" и "ошибка", вам, вероятно, следует использовать первый случай (придерживайтесь идиомы / соглашения), но в других случаях у вас может быть несколько вариантов "хороших данных". Мы можем использовать интерфейсы для решения этой проблемы. Здесь мы добавляем фиктивный метод, чтобы сказать компилятору ограничить возможные типы, которые могут реализовать этот интерфейс, теми, у которых есть метод 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. Помеченный Союз ( детская площадка)
Этот случай аналогичен предыдущему, за исключением того, что вместо интерфейса мы используем структуру с тегом, который сообщает нам, какой тип данных содержит структура (это похоже на теговое объединение в 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
}
// 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 разных типа, которые не "присваиваются" одной и той же переменной... если вы не используете конкретную сигнатуру интерфейса или пустой интерфейс.
этот код будет компилироваться:
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, она является единственным авторитетом и довольно хорошо написана по сравнению с большинством спецификаций. По большей части правила кристально чисты, и это сила Go.