2016-09-27 5 views
1

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

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

кажется, что я не мог назначить response вар данных с разным типом (в моем случае DataError) , Я слышал, что возможным решением может быть объединение Data и DataError структур через интерфейс. Или, может быть, есть еще одно лучшее решение?

Не могли бы вы указать мне, как решить эту проблему?

Благодаря

+2

Нет, вы не можете назначать разные типы одной и той же переменной. Вы можете пустым 'interface {}' (https://play.golang.org/p/h5vPe3Et1O), но это не очень хорошее решение. В Go вы обычно используете отдельные значения ошибок. Прошли ли вы через вводные материалы? – JimB

+0

@JimB, да, но интерфейсы для меня до сих пор не ясны :) – tsg

+0

Нет проблем, так как нет необходимости объединять эти два типа. – Volker

ответ

2

Похоже, вы пытаетесь создать тип объединения (то, что ML-семейство языков называет «enum»). Я знаю пару моделей для этого:

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

Этот случай похож на предыдущий случай, за исключением того, что вместо использования интерфейса мы используем структуру с тегом, которая сообщает нам, какой тип данных содержит структура (это похоже на меченый союз в C, за исключением того, что размер структуры представляет собой сумму его потенциальных типов, а не размер его наибольшего потенциального типа). В то время как это занимает больше места, его можно легко распределить по стеклу, тем самым делая его дружественным по течению (я использовал этот метод для сокращения выделов от O (n) до O (1)). В этом случае наш тег является bool, потому что у нас есть только два возможных типа (Data and 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)) 
} 
+0

это именно то, что мне нужно! большое спасибо! Очень подробный ответ :) – tsg

+0

@tsg Счастливые помочь. :) – weberc2

1

Вы не можете назначить 2 различных типов, которые не являются «переуступке» одной и той же переменной ... если вы не используете определенный интерфейс подписи или пустой интерфейс.

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.

+0

спасибо за ваш быстрый ответ. Это очень интересно для меня :) – tsg