使用嵌入的数据和别名类型简化代码
注意:这里提到的“别名”与 Go 1.9 的别名提议不同。这个“别名”只是简单的指向某个新类型,它具有与另一种类型相同的数组,但有不同的方法集。
正如我们之前所见,在把所有数据从一种类型复制到另一种类型的时候,定义字段的过程相当乏味。进一步放大来看,如果我们要处理拥有10个或20个字段的对象,保持 JSONDog 和 Dog 类型同步的过程就会让人觉得厌烦。
幸好有另一种方法来解决这个问题,它可以减少需要我们定义的字段,只处理那些需要定义编辑和解码的字段。我们会把 Dog 对象嵌入到 JSONDog 中去,然后自定义一些需要自定义的字段。
一开始需要更新 Dog 类型,为其加入 JSON 标签,这些标签加在需要自定义的字段后面。然后我们将告诉 / json 包忽略字段,通过使用结构标签 json:"-" 来提醒 JSON 编码器应该忽略这个字段,即使它被导出。
type Dog struct {
ID int `json:"id"`
Name string `json:"name"`
Breed string `json:"breed"`
BornAt time.Time `json:"-"`
}
接下来,我们把 Dog 类型嵌入到 JSONDog 类型中,并更新 NewJSONDog() 函数和 JSONDog 类型的 Dog() 方法。我们临时把 Dog() 方法改名为 ToDog(),避免与内部的 Dog 对象冲突。
警告:代码现在不能工作,我展示了中间过程来说明其原因。
func NewJSONDog(dog Dog) JSONDog {
return JSONDog{
dog,
dog.BornAt.Unix(),
}
}
type JSONDog struct {
Dog
BornAt int64 `json:"born_at"`
}
func (jd JSONDog) ToDog() Dog {
return Dog{
jd.Dog.ID,
jd.Dog.Name,
jd.Dog.Breed,
time.Unix(jd.BornAt, 0),
}
}
它可以编译,但如果尝试运行的话会产生一个致命错误:堆栈溢出。这发生在调用 Dog 类型的 MarshaJSON() 方法的时候。当函数调用的时候,它会构造一个 JSONDog,但是这个对象内部有一个 Dog 对象,构造 Dog 对象的时候又会构造新的 JSONDog,这就产生了一个无限循环,直到程序崩溃。
为了避免这种情况发生,我们需要创建一个 Dog 类型的别名,它不包含 MarshalJSON() 和 UnmarsshalJSON() 方法。
type DogAlias Dog
拥有了别名类型之后,就可以更新 JSONDog 类型,用它来代替 Dog 类型。我们也需要更新 NewJSONDog(),将 Dog 改为 DogAlias,然后可以清理一下 JSONDOg 类型的 Dog() 方法,将内部的 Dog 作为返回值。
func NewJSONDog(dog Dog) JSONDog { return JSONDog{
DogAlias(dog),
dog.BornAt.Unix(),
}
}type JSONDog struct {
DogAlias
BornAt int64 `json:"born_at"`}func (jd JSONDog) Dog() Dog {
dog := Dog(jd.DogAlias)
dog.BornAt = time.Unix(jd.BornAt, 0) return dog
}
如你所见,初始设置需要大约花了30行代码,但现在我们已经设置好了,Dog 类型中有多少字段并不重要。JSON 代码只会在需要自定义 JSON 的字段增加时才会增长。
你可以在 Go 实验场找到这一节的所有代码:https://play.golang.org/p/N0rweY-cD0