让业务代码更优雅的方法——类型工厂

1. 问题来源

最近碰到一个需求,用结构化数据结构(如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函数会变得多么庞大和难以维护。

2.  解决方法

可以看到,以上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文件即可,不需要在任何外部文件增加任何一行代码,代码可读性,可维护性,可谓完美。

 

你可能感兴趣的:(优雅代码,golang)