Как объединить две разные структуры с помощью интерфейса?

У меня есть следующий код:

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 разных типа, которые не "присваиваются" одной и той же переменной... если вы не используете конкретную сигнатуру интерфейса или пустой интерфейс.

https://golang.org/ref/spec

этот код будет компилироваться:

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.

Другие вопросы по тегам