我正在使用gob将结构序列化到磁盘。有问题的结构包含一个接口字段,因此具体类型需要使用进行注册gob.Register(...)。
gob.Register(...)
这里的难题是,进行灌装的图书馆应忽略所使用的混凝土类型。我希望即使调用者定义了自己的接口实现,也可以进行序列化。
我可以通过动态注册类型来成功编码数据(请参见下面的简单示例),但是在尝试重新读取数据时,gob拒绝接受未注册的类型。它令人沮丧,因为感觉好像所有数据都在那里- main.UpperCaseTransformation如果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注册其类型。但是我不喜欢这样向调用者显示我的序列化选择。
有没有避免这种情况的前进路线?
该encoding/gob程序包不能(或者应该不应该)自己做出决定。由于该gob包创建了独立于应用程序或与应用程序分离的序列化形式,因此无法保证解码器中将存在接口类型的值;即使它们确实做到了(与具体类型名称匹配),也无法保证它们代表相同的类型(或给定类型的相同实现)。
encoding/gob
gob
通过调用gob.Register()(或gob.RegisterName())您可以清楚地表明 意图 ,您可以使gob包装使用绿灯。这还可以确保该类型 确实 存在,否则在注册时您将无法传递其值。
gob.Register()
gob.RegisterName()
还有一种技术观点规定了此要求(必须事先注册):您无法获得reflect.Type由其string名称给出的类型的类型描述符。不仅您自己,encoding/gob包装也不能做到。
reflect.Type
string
因此,通过要求您gob.Register()事先调用,gob程序包将接收到相关类型的值,因此它可以(并且将)在reflect.Type内部访问和存储其描述符,因此,当检测到该类型的值时,它就可以创建这种类型的新值(例如使用reflect.New())的过程,以便将要解码的值存储到其中。
reflect.New()
您不能按名称“查找”类型的原因是,除非您明确引用它们,否则它们可能不会出现在二进制文件中(它们可能会被“优化”)。注册自定义类型时(通过传递它们的值),您将对它们进行显式引用,从而确保它们不会从二进制文件中排除。