Расшифровать вывод gob, не зная конкретных типов
Я использую gob для сериализации структур на диск. Рассматриваемая структура содержит поле интерфейса, поэтому конкретный тип должен быть зарегистрирован с использованием gob.Register(...)
,
Проблема заключается в том, что библиотека, выполняющая операции, должна не знать о конкретном используемом типе. Я хотел, чтобы сериализация была возможна, даже когда вызывающие стороны определили свои собственные реализации интерфейса.
Я могу успешно закодировать данные, зарегистрировав тип на лету (см. Тривиальный пример ниже), но при попытке перечитать эти данные, gob отказывается принять незарегистрированный тип. Это разочаровывает, потому что кажется, что все данные есть - почему бы просто не распаковать это как main.UpperCaseTransformation
структура, если она помечена как таковая?
package main
import (
"encoding/gob"
"fmt"
"os"
"strings"
)
type Transformation interface {
Transform(s string) string
}
type TextTransformation struct {
BaseString string
Transformation Transformation
}
type UpperCaseTransformation struct{}
func (UpperCaseTransformation) Transform(s string) string {
return strings.ToUpper(s)
}
func panicOnError(err error) {
if err != nil {
panic(err)
}
}
// Execute this twice to see the problem (it will tidy up files)
func main() {
file := os.TempDir() + "/so-example"
if _, err := os.Stat(file); os.IsNotExist(err) {
tt := TextTransformation{"Hello, World!", UpperCaseTransformation{}}
// Note: didn't need to refer to concrete type explicitly
gob.Register(tt.Transformation)
f, err := os.Create(file)
panicOnError(err)
defer f.Close()
enc := gob.NewEncoder(f)
err = enc.Encode(tt)
panicOnError(err)
fmt.Println("Run complete, run again for error.")
} else {
f, err := os.Open(file)
panicOnError(err)
defer os.Remove(f.Name())
defer f.Close()
var newTT TextTransformation
dec := gob.NewDecoder(f)
// Errors with: `gob: name not registered for interface: "main.UpperCaseTransformation"'
err = dec.Decode(&newTT)
panicOnError(err)
}
}
Мой обходной путь - требовать, чтобы разработчики интерфейса регистрировали свой тип с помощью gob. Но мне не нравится, как это показывает мой выбор сериализации звонящим.
Есть ли какой-нибудь путь вперед, который избегает этого?
1 ответ
Философская аргументация
encoding/gob
Пакет не может (или, скорее, не должен) принимать это решение самостоятельно. Так как gob
package создает сериализованную форму, независимую от приложения или отсоединенную от него, нет гарантии, что значения типов интерфейса будут существовать в декодере; и даже если они это делают (соответствуют конкретному имени типа), нет гарантии, что они представляют один и тот же тип (или одну и ту же реализацию данного типа).
По телефону gob.Register()
(или же gob.RegisterName()
Вы даете понять это намерение, вы даете зеленый свет gob
пакет для использования этого типа. Это также гарантирует, что тип существует, иначе вы не сможете передать его значение при регистрации.
Техническое требование
Существует также техническая точка зрения, которая диктует это требование (которое необходимо предварительно зарегистрировать): вы не можете получить reflect.Type
дескриптор типа типа, заданного его string
название. Не только ты, encoding/gob
Пакет тоже не может этого сделать.
Так что, требуя от вас позвонить gob.Register()
до gob
пакет получит значение типа, о котором идет речь, и поэтому он может (и будет) обращаться к нему и сохранять его reflect.Type
дескриптор внутри, и поэтому, когда значение этого типа обнаружено, оно способно создать новое значение этого типа (например, используя reflect.New()
) для того, чтобы сохранить декодируемое в него значение.
Причина, по которой вы не можете "искать" типы по имени, заключается в том, что они могут не оказаться в вашем двоичном файле (они могут быть "оптимизированы"), если вы явно не обратитесь к ним. Подробнее см. Вызов всех функций со специальным префиксом или суффиксом в Golang; и Разделение клиент-серверного кода. Регистрируя свои пользовательские типы (передавая их значения), вы делаете явную ссылку на них и тем самым гарантируете, что они не будут исключены из двоичных файлов.