如何在 go 中保存和加载接口结构

How to save and load a structure of interfaces in go

来自python 我习惯用自定义方法写一个class。启动它,保存对象并再次加载它。我正在尝试完成类似的事情。在阅读了有关序列化的信息后,我尝试使用 marshall/unmarshall 方法。

但下面的代码 deos 不起作用,因为它导致 method.Method = nil。我希望能够调用 method.Method.Calculcate()

下面是我的尝试。

import (
    "encoding/json"
    "fmt"
    "log"
)

type Calculator struct {
    Method Method `json:"Method"`
}

type method1 struct {
    Input string `json:"input"`
}

func NewMethod1(input string) Method {
    return &method1{
        Input: input,
    }
}

type method2 struct {
    Input string `json:"input"`
}

func NewMethod2(input string) Method {
    return &method2{
        Input: input,
    }
}

type Method interface {
    Calculcate()
}

func (m *method1) Calculcate() {
}
func (m *method2) Calculcate() {
    }

func main() {
    model := Calculator{
        Method: NewMethod1("inputData"),
    }
    model.Method.Calculcate()
    var jsonData []byte
    jsonData, err := json.Marshal(model)
    if err != nil {
        log.Println(err)
    }
    fmt.Println(string(jsonData))

    var method Calculator
    json.Unmarshal(jsonData, &method)
    if err != nil {
        log.Println(err)
    }
}

我想加载结构并在计算时使用与方法 2 相同的代码来启动方法 1。这意味着我事先不知道我加载的是方法 1 还是方法 2。下面是一些解释我想要什么的伪代码。

Calculator :=Load Calculator()
model := Calculator{
    Method: NewMethod1("inputData"),
}
model.Method.Calculcate()

问题是解码一个JSON不知道当一个字段有一个接口类型时使用什么具体类型(多个类型可能实现一个接口)。这在您的示例中可能很明显,但再想一想:您将此 JSON 发送到另一台尝试对其进行解码的计算机。 JSON表示没有记录接口值的具体类型(在本例中是*main.method1),所以另一端不知道要实例化什么接口字段 Calculator.Method.

如果您需要这种序列化和反序列化,请改用 encoding/gob 包。该包还写入了类型信息,因此解码器部分 至少知道类型名称 encoding/gob 不传输类型定义,因此如果另一部分具有不同版本的类型,该过程也会失败。这记录在包文档中:

Interface types are not checked for compatibility; all interface types are treated, for transmission, as members of a single "interface" type, analogous to int or []byte - in effect they're all treated as interface{}. Interface values are transmitted as a string identifying the concrete type being sent (a name that must be pre-defined by calling Register), followed by a byte count of the length of the following data (so the value can be skipped if it cannot be stored), followed by the usual encoding of concrete (dynamic) value stored in the interface value. (A nil interface value is identified by the empty string and transmits no value.) Upon receipt, the decoder verifies that the unpacked concrete item satisfies the interface of the receiving variable.

此外,为了使 encoding/gob 包能够根据其名称实例化类型,您必须注册这些类型,更具体地说是这些类型的值。

这是一个使用 encoding/gob 的工作示例:

首先让我们改进Calculate()方法,看看它们被调用:

func (m *method1) Calculcate() {
    fmt.Println("method1.Calculate() called, input:", m.Input)
}

func (m *method2) Calculcate() {
    fmt.Println("method2.Calculate() called, input:", m.Input)
}

现在序列化/反序列化过程:

// Register the values we use for the Method interface
gob.Register(&method1{})
gob.Register(&method2{})

model := Calculator{
    Method: NewMethod1("inputData"),
}
model.Method.Calculcate()

buf := &bytes.Buffer{}
enc := gob.NewEncoder(buf)
if err := enc.Encode(model); err != nil {
    log.Println(err)
    return
}
fmt.Println(buf.Bytes())

var model2 Calculator
dec := gob.NewDecoder(buf)
if err := dec.Decode(&model2); err != nil {
    log.Println(err)
    return
}
model2.Method.Calculcate()

这将输出(在 Go Playground 上尝试):

method1.Calculate() called, input: inputData
[35 255 129 3 1 1 10 67 97 108 99 117 108 97 116 111 114 1 255 130 0 1 1 1 6 77 101 116 104 111 100 1 16 0 0 0 48 255 130 1 13 42 109 97 105 110 46 109 101 116 104 111 100 49 255 131 3 1 1 7 109 101 116 104 111 100 49 1 255 132 0 1 1 1 5 73 110 112 117 116 1 12 0 0 0 16 255 132 12 1 9 105 110 112 117 116 68 97 116 97 0 0]
method1.Calculate() called, input: inputData