最近碰到一个需求,用结构化数据结构(如JSON)构造一个表达式。
已设计数据结构如下:
// Value represents a value with given type
type Value struct {
// type of this value:
// number|string|boolean|object|array|expr
Type string `json:"type"`
Data json.RawMessage `json:"data"` // specific value for non-field content
}
问题来了,Data字段根据Type值的内容,需要反序列化为不同的数据结构。
方法很简单:
func (v *Value) UnmarshalData() (interface{}, error) {
var d interface{}
switch v.Type {
case "number":
x := float64(0)
d = &x
case "string":
x := ""
d = &x
case "object":
x := &Object{}
d = x
case "array":
x := &Array{}
d = x
case "expr":
x := &Expr{}
d = x
case ...
}
err:=json.Unmarshal(v.Data, d)
return d, err
}
如果你有代码洁癖,你会发现这个switch-case非常刺眼。
设想,如果需要设计的类型有成百上千种,这个UnmarshalData函数会变得多么庞大和难以维护。
可以看到,以上switch-case所做的事情,不外乎是输入一个字符串,输出一个interface{}。
那么有么有可能用一个函数来代替呢,答案是肯定的:
// Factory impliments a factory that can create multi products by type name
type Factory struct {
mp map[string]reflect.Type
name string
}
// newJsonFactory create a new factory
func NewFactory(name string) *Factory {
return &Factory{
mp: make(map[string]reflect.Type),
name: name,
}
}
// MustReg register the creator by name, it panic if name is duplicate
func (f *Factory) MustReg(name string, v interface{}) {
if _, ok := f.mp[name]; ok {
panic(fmt.Errorf("duplicate reg of %s,%#v", v.TypeName(), v))
}
t := reflect.TypeOf(v)
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
f.mp[name] = t
return nil
}
// Create make product by name
func (f *Factory) Create(name string) (interface{}, error) {
t, ok := f.mp[name]
if !ok {
return nil, fmt.Errorf("product [%s] cannot create from factory %s", name, f.name)
}
return reflect.New(t).Interface(), nil
}
业务代码可以简化为:
// factory regist some flex json objects
var factory = NewFactory("flexObjCreator")
func init() {
factory.MustReg("number", (*ValNumber)(nil))
factory.MustReg("string", (*ValString)(nil))
factory.MustReg("boolean", (*ValBoolean)(nil))
factory.MustReg("object", (*ValObject)(nil))
factory.MustReg("array", (*ValArray)(nil))
factory.MustReg("expr", (*Expr)(nil))
}
func (v *Value) UnmarshalData() (interface{}, error) {
d, err := factory.Create(v.Type)
if err != nil {
return nil, err
}
err := json.Unmarshal(v.Data, d)
return d, err
}
可以看到,业务代码已经简单到增加一个类型只需要增加一行注册函数调用。
更有甚者,这个init函数可以拆分到每个类型的实现文件中去分散注册。
这样扩展一个类型的时候,只需要新增一个value_xxx.go文件即可,不需要在任何外部文件增加任何一行代码,代码可读性,可维护性,可谓完美。