golang组合、接口以及反序列化
现在,我想用Go实现一个Event
类,有如下的要求:
- 有多种Event类型,不同的Event类型拥有不同的参数;
- 这些Event会被持久化到存储中,在需要时反序列化回来;
- 每个Event都有Process方法,具体的方法实现逻辑存放在不同的Event具体类型中。
ok,看需求并不复杂,不就是面向对象的继承、重载那一套吗,so easy,直接开搞! 但是真正实现时,才发现没有想象中的那么简单…
1. 结构体实现
首先我们使用go来实现结构体Event
,然后利用组合的方式实现两个Event
:
type Event struct {
EventType string `json:"event_type"`
}
type ClickEvent struct {
Event `json:",inline"`
OnPosX int64 `json:"on_pos_x"`
OnPosY int64 `json:"on_pos_y"`
}
type DragEvent struct {
Event `json:",inline"`
OnPosX int64 `json:"on_pos_x"`
OnPosY int64 `json:"on_pos_y"`
ToPosX int64 `json:"to_pos_x"`
ToPosY int64 `json:"to_pos_y"`
}
很简单,没有问题。
2. 序列化与反序列化
让我们尝试将其序列化到json,以便持久化存储:
func main() {
event1 := ClickEvent{
Event: Event{EventType: "click"},
OnPosX: 0,
OnPosY: 0,
}
b1, _ := json.Marshal(&event1)
event2 := DragEvent{
Event: Event{EventType: "drag"},
OnPosX: 1,
OnPosY: 2,
ToPosX: 3,
ToPosY: 4,
}
b2, _ := json.Marshal(&event2)
fmt.Println(string(b1))
fmt.Println(string(b2))
}
看上去也不难。对每个具体的Event直接json反序列化即可。以上程序会输出如下的序列化之后的字符串:
{"event_type":"click","on_pos_x":0,"on_pos_y":0}
{"event_type":"drag","on_pos_x":1,"on_pos_y":2,"to_pos_x":3,"to_pos_y":4}
那如果反序列化呢?
为了使程序更加通用,我们假设并不清楚读取得到的json究竟是哪一种事件类型。一般会有一个函数能反序列化所有的事件类型,根据其中的EventType
类型不同反序列化成不同的事件类型的实例。
让我们先试试直接反序列化成组合的根结构体会怎么样?
func main() {
jsonStr := `{"event_type":"click","on_pos_x":10,"on_pos_y":20}`
event, err := unmarshalEvent(jsonStr)
if err != nil {
panic(err)
}
fmt.Printf("%+v\n", event)
fmt.Println(event.(*ClickEvent).OnPosX) //备注2
}
func unmarshalEvent(jStr string) (*Event, error) {
var tmpEvent Event // //备注1
err := json.Unmarshal([]byte(jStr), &tmpEvent)
return &tmpEvent, err
}
在备注2的位置我们遇到了麻烦。这里json从类型上看是ClickEvent
类型,但是使用组合结构体Event
进行反序列化之后并没有得到想要的类型。该Event
无法强制转换为ClickEvent
类型,就算ClickEvent
中组合了Event
也不行。我们需要另想办法。
需要注意的是,以上程序报的错误是
invalid type assertion: tmpEvent.(*ClickEvent) (non-interface type Event on left)
这里似乎说的是备注1的位置没有使用interface{}
导致的该问题。但是就算改成了var tmpEvent interface{}
, 也依旧会报错,变成如下错误:
panic: interface conversion: interface {} is map[string]interface {}, not *main.ClickEvent
也就是说json直接被反序列化成了map[string]interface {}
,似乎更糟了。
这里有一种解决方案,就是在unmarshalEvent
函数中先反序列化为Event
,然后再根据其类型反序列化为其他类型。代码如下:
func main() {
jsonStr := `{"event_type":"click","on_pos_x":10,"on_pos_y":20}`
event, err := unmarshalEvent(jsonStr)
if err != nil {
panic(err)
}
fmt.Println(reflect.TypeOf(event)) //备注2
switch event.(type) { //备注3
case *ClickEvent:
fmt.Println(event.(*ClickEvent).OnPosX)
case *DragEvent:
fmt.Println(event.(*DragEvent).ToPosX)
default:
// todo more judge
}
}
func unmarshalEvent(jStr string) (interface{}, error) {
var tmpEvent Event
if err := json.Unmarshal([]byte(jStr), &tmpEvent); err != nil {
return nil, err
}
switch tmpEvent.EventType { // 备注1
case "click":
var clickEvent ClickEvent
err := json.Unmarshal([]byte(jStr), &clickEvent)
return &clickEvent, err
case "drag":
var dragEvent DragEvent
err := json.Unmarshal([]byte(jStr), &dragEvent)
return &dragEvent, err
default:
//todo another type
}
return nil, errors.New("invalid type:" + tmpEvent.EventType)
}
代码可以正常运行,我们成功地识别出了json中的Event
类型,并将其反序列化成了想要的具体类型。
由备注2的输出可以看到,我们得到的类型的确是*ClickEvent
,而不是*Event
。
这里有两个问题:
- 我们需要再反序列化时枚举所有可能的
Event
类型。也就是说每当我需要新增一个Event
类型,都需要修改备注1的代码,不符合开闭原则; - 由于go无法在组合结构体之间相互赋值,
unmarshalEvent
的返回类型是一个interface{}
,这样就无可避免地需要在备注3处进行类型判断和类型强转,导致代码冗长。
第一个问题我们暂时先放一放;对于第二个问题,我们能否使用接口来解决?
3. 使用接口实现扩展
我们修改一下第1节的结构体定义,改成使用接口而非组合的方式进行Event
的扩展:
type Event interface {
GetEventType() string
Process()
}
type ClickEvent struct {
OnPosX int64 `json:"on_pos_x"`
OnPosY int64 `json:"on_pos_y"`
}
func (e *ClickEvent) GetEventType() string {
return "click"
}
func (e *ClickEvent) Process() {
fmt.Println("process ClickEvent with on pos: ", e.OnPosX, ", ", e.OnPosY)
}
func (e *ClickEvent) MarshalJSON() ([]byte, error) {
type copyType ClickEvent
return json.Marshal(struct {
copyType
EventType string `json:"event_type"`
}{
copyType: copyType(*e),
EventType: "click",
})
}
type DragEvent struct {
OnPosX int64 `json:"on_pos_x"`
OnPosY int64 `json:"on_pos_y"`
ToPosX int64 `json:"to_pos_x"`
ToPosY int64 `json:"to_pos_y"`
}
func (e *DragEvent) GetEventType() string {
return "drag"
}
func (e *DragEvent) Process() {
fmt.Println("process DragEvent with to pos: ", e.ToPosX, ", ", e.ToPosY)
}
func (e *DragEvent) MarshalJSON() ([]byte, error) {
type copyType DragEvent
return json.Marshal(struct {
copyType
EventType string `json:"event_type"`
}{
copyType: copyType(*e),
EventType: "drag",
})
}
在这里,Event
变成了一个接口而非结构体,除了提供GetEventType
方法之外,也定义了必须实现的Process
方法。注意其中为了保证序列化结果中包含event_type
,这里重新定义了ClickEvent
和DragEvent
的MarshalJSON
方法。
使用接口的方式实现的各个事件定义的序列化的使用方式和之前没有什么不同,这里直接看反序列化:
func main() {
jsonStr := `{"event_type":"click","on_pos_x":10,"on_pos_y":20}`
event, err := unmarshalEvent(jsonStr)
if err != nil {
panic(err)
}
fmt.Println(reflect.TypeOf(event))
switch event.GetEventType() { //备注3
case "click":
fmt.Println(event.(*ClickEvent).OnPosX)
case "drag":
fmt.Println(event.(*DragEvent).ToPosX)
default:
// todo more judge
}
event.Process() //备注4
}
func unmarshalEvent(jStr string) (Event, error) {
var tmpEvent map[string]interface{} // 备注1
if err := json.Unmarshal([]byte(jStr), &tmpEvent); err != nil {
panic(err)
}
switch tmpEvent["event_type"].(string) { // 备注2
case "click":
var clickEvent ClickEvent
err := json.Unmarshal([]byte(jStr), &clickEvent)
return &clickEvent, err
case "drag":
var dragEvent DragEvent
err := json.Unmarshal([]byte(jStr), &dragEvent)
return &dragEvent, err
default:
//todo another type
}
return nil, errors.New("invalid type:" + tmpEvent["event_type"].(string))
}
这里将unmarshalEvent
方法的返回值类型由interface
变更为了Event
。代价就是在判断当前事件类型时不能使用Event
接收反序列化结果了,备注1处和备注2处必须使用map[string]interface{}
,稍微复杂了些。
在备注3处,为了获取原始的事件,还是需要进行类型的探测以及强转,不过这里好一点的是无需进行类型检测,只需要检测event.GetEventType()
即可。另外在备注4处,我们可以无需进行类型探测和强转即可去调用各个实现事件的Process
函数,这个就比之前使用interface{}
的方式好很多。这真是接口"抽象行为"上的优势体现。但是使用接口的方式问题在于无法提取共性的参数进行复用,可以结合使用"组合"和"接口"两种方式来获取二者的优势。
4. 支持自定义的事件
到目前为止看来问题几乎已经完全得到了解决,除了上一章提到的一个问题:
我们需要再反序列化时枚举所有可能的
Event
类型。也就是说每当我需要新增一个Event
类型,都需要修改备注1的代码,不符合开闭原则。
在我们需要支持用户自定义事件时,每次新增都需要修改一次unmarshalEvent
的函数代码。这个该怎么改进呢?
退一步想一下,如果我自定义了一个事件,然后在unmarshalEvent
中反序列化一个json成为这个自定义事件的类型实例,需要哪些必要的信息?
首先很明确的一点,就是当前提供的json具体是哪个类型,这个信息通过字段event_type
已经可以知道了;其次,如果是自定义的扩展类型,还需要的数据就是“当前扩展类型的结构体定义”,或者说有一种机制能够让unmarshalEvent
在读取到event_type
之后还能够知道这种event_type
对应的类型就行了。
这样看的话,我们可以为unmarshalEvent
函数维护一个可以扩展的映射,维护event_type
到具体结构体类型的映射关系。在我们新增一个event_type
及其定义时,将其注册到这个扩展映射中即可。这样的话在事件类型扩展时unmarshalEvent
本身不用做任何改动。
var mutex sync.RWMutex
var extendEventType map[string]reflect.Type
func RegisterExtendEvent(eventType string, p reflect.Type) {
mutex.Lock()
defer mutex.Unlock()
if extendEventType == nil {
extendEventType = make(map[string]reflect.Type)
}
extendEventType[eventType] = p
}
func GetEventByType(eventType string) (reflect.Type, error) {
mutex.RLock()
defer mutex.RUnlock()
if extendEventType == nil || extendEventType[eventType] == nil {
return nil, errors.New("Unregistered extendEventType:" + eventType)
}
return extendEventType[eventType], nil
}
func unmarshalEvent(jStr string) (Event, error) {
var tmpEvent map[string]interface{}
if err := json.Unmarshal([]byte(jStr), &tmpEvent); err != nil {
panic(err)
}
eventType := tmpEvent["event_type"].(string)
t, err := GetEventByType(eventType)
if err != nil {
return nil, err
}
extendEvent := reflect.New(t).Interface()
err = json.Unmarshal([]byte(jStr), &extendEvent)
return extendEvent.(Event), err
}
然后再需要扩展Event
类型时进行注册即可:
RegisterExtendEvent("click", reflect.TypeOf(ClickEvent{}))
RegisterExtendEvent("drag", reflect.TypeOf(DragEvent{}))
到目前为止,我们似乎完成了所有的需求。看上去不错,但这里有一个限制就是unmarshalEvent()
结果获取的是Event
接口,也就是说我们可以调用其中的接口方法,但是无法调用实际类型的方法(强制转换后是可以的,但是还是需要switch判断实际类型后进行转换,不是我所需要的)。
假如我们需要的通用操作不只是Process()
,需要扩展一个新的操作Cancel()
,这个时候只有两种办法:
要么修改Event
接口定义然后实现:
type Event interface {
GetEventType() string
Process()
Cancel()
}
要么我重新定义一个Canceler
接口:
type Canceler interface {
Cancel()
}
重新定义一个Canceler
接口是不合适的,unmarshalEvent()
无法返回多个接口的变量,毕竟没有泛型;而如果我修改Event
接口定义,那么我需要修改所有的Event
实现,这工作量也太大了些。
参考接口类型的注册模式,我们也可以定义一个不同操作的注册机制。Event
实现类只需要在需要的时候调用操作的注册方法即可;而在相应方法未注册时进行查找调用的话会忽略或者抛出异常:
var handleMutex sync.RWMutex
var handlerMap map[string]map[string]func(e Event)
func RegisterEventHandler(eventType string, handlerName string, f func(e Event)) {
handleMutex.Lock()
defer handleMutex.Unlock()
if handlerMap == nil {
handlerMap = make(map[string]map[string]func(e Event))
}
if handlerMap[eventType] == nil {
handlerMap[eventType] = make(map[string]func(e Event))
}
handlerMap[eventType][handlerName] = f
}
func GetEventHandler(eventType string, handlerName string) (f func(e Event), error) {
handleMutex.RLock()
defer handleMutex.RUnlock()
if handlerMap == nil || handlerMap[eventType] == nil {
return nil, errors.New("Unregistered handler for eventType:" + eventType + " and handlerName:" + handlerName)
}
return handlerMap[eventType][handlerName], nil
}
然后记得注册和使用即可:
func ProcessClickEvent(e Event) {
ce := e.(*ClickEvent)
fmt.Println("process ClickEvent with on pos: ", ce.OnPosX, ", ", ce.OnPosY)
}
func CancelClickEvent(e Event) {
ce := e.(*ClickEvent)
fmt.Println("cancel ClickEvent with on pos: ", ce.OnPosX, ", ", ce.OnPosY)
}
func ProcessDragEvent(e Event) {
de := e.(*DragEvent)
fmt.Println("process DragEvent with to pos: ", de.ToPosX, ", ", de.ToPosY)
}
func CancelDragEvent(e Event) {
de := e.(*DragEvent)
fmt.Println("cancel DragEvent with to pos: ", de.ToPosX, ", ", de.ToPosY)
}
func main() {
RegisterExtendEvent("click", reflect.TypeOf(ClickEvent{}))
RegisterExtendEvent("drag", reflect.TypeOf(DragEvent{}))
RegisterEventHandler("click", "process", ProcessClickEvent)
RegisterEventHandler("click", "cancel", CancelClickEvent)
RegisterEventHandler("drag", "process", ProcessDragEvent)
RegisterEventHandler("drag", "cancel", CancelDragEvent)
jsonStr := `{"event_type":"click","on_pos_x":10,"on_pos_y":20}`
event, err := unmarshalEvent(jsonStr)
if err != nil {
panic(err)
}
processFunc, err := GetEventHandler(event.GetEventType(), "process")
if err != nil {
panic(err)
}
if processFunc != nil {
processFunc(event)
}
}
5. 自定义反序列化
前面几节使用了unmarshalEvent
进行了事件的反序列化。我们的Event
是独立的实体还好说,如果是某个结构体的一部分怎么办?这时候就必须自定义json的反序列化方法了。
比如我们有如下的结构体,然后尝试反序列化:
type Panel struct {
Height int64 `json:"height"`
Row int64 `json:"row"`
Event Event `json:"event"`
}
func main() {
var panel Panel
panelJson := `{"height": 200, "row": 100, "event": {"event_type":"click","on_pos_x":10,"on_pos_y":20}}`
if err := json.Unmarshal([]byte(panelJson), &panel); err != nil {
panic(err)
}
fmt.Println(reflect.TypeOf(panel.Event))
}
很可惜,上面的程序报错了:
panic: json: cannot unmarshal object into Go struct field Panel.event of type main.Event
也就是说,Panel
的Event
无法反序列化。这是就需要自定义反序列化函数。为了Event
的自定义反序列化不影响Panel
的其他正常字段,这里可以使用一个小技巧:将Event
包装起来:
type Panel struct {
Height int64 `json:"height"`
Row int64 `json:"row"`
Event EventWrapper `json:"event"`
}
type EventWrapper struct {
Event Event
}
func (w EventWrapper) MarshalJSON() ([]byte, error) {
return json.Marshal(w.Event)
}
func (w *EventWrapper) UnmarshalJSON(data []byte) error {
var tmpEvent map[string]interface{}
if err := json.Unmarshal(data, &tmpEvent); err != nil {
return err
}
eventType := tmpEvent["event_type"].(string)
t, err := GetEventByType(eventType)
if err != nil {
return err
}
extendEvent := reflect.New(t).Interface()
err = json.Unmarshal(data, &extendEvent)
if err != nil {
return err
}
w.Event = extendEvent.(Event)
return nil
}
func main() {
RegisterExtendEvent("click", reflect.TypeOf(ClickEvent{}))
RegisterExtendEvent("drag", reflect.TypeOf(DragEvent{}))
var panel Panel
panelJson := `{"height": 200, "row": 100, "event": {"event_type":"click","on_pos_x":10,"on_pos_y":20}}`
if err := json.Unmarshal([]byte(panelJson), &panel); err != nil {
panic(err)
}
fmt.Println(reflect.TypeOf(panel.Event))
b, err := json.Marshal(panel)
if err != nil {
panic(err)
}
fmt.Println(string(b))
}
回过头来,如果我们从一开始不是使用接口而是直接使用结构体来定义Event
,事情又会变得不一样。我们只需要处理一件事情,那就是结构体之间的相互转换。
type Event struct {
EventType string `json:"event_type"`
EventParam interface{} `json:"event_param"`
}
func (e *Event) UnmarshalJSON(data []byte) error {
// 使用新类型,避免递归调用
type cloneType Event
rawMsg := json.RawMessage{}
e.EventParam = &rawMsg //将Param强制赋值为json.RawMessage,避免使用默认的map[string]interface{}
if err := json.Unmarshal(data, (*cloneType)(e)); err != nil {
return err
}
t, err := GetEventByType(e.EventType)
if err != nil {
return err
}
extendEvent := reflect.New(t).Interface()
if len(rawMsg) != 0 {
if err := json.Unmarshal(rawMsg, &extendEvent); err != nil { //使用rawMsg进行序列化,rawMsg由前面的反序列化得来
return err
}
}
e.EventParam = extendEvent
return nil
}
func main() {
RegisterExtendEvent("click", reflect.TypeOf(ClickEvent{}))
RegisterExtendEvent("drag", reflect.TypeOf(DragEvent{}))
jsonStr := `{"event_type":"click", "event_param": {"on_pos_x":10,"on_pos_y":20}}`
var event Event
err := json.Unmarshal([]byte(jsonStr), &event)
if err != nil {
panic(err)
}
fmt.Println(reflect.TypeOf(event))
fmt.Println(event.EventParam.(*ClickEvent).OnPosX)
}
这个时候无需包装结构体,在反序列化的时候使用RawMessage
进行延迟处理即可。
6. 总结
由于go语言没有完整的面向对象的类型支持和泛型支持,所以在处理时需要自己考虑持久化的数据结构如何以及如何反序列化回来,还有就是怎么进行灵活的类型和方法绑定。最后的实现方式似乎越来越接近面向对象支持的底层实现,说不定有一天看到c++的面向对象实现原理,会有一种“啊,这一块的实现方式我之前也有写过”的奇妙感觉。