Event

ok,看需求并不复杂,不就是面向对象的继承、重载那一套吗,so easy,直接开搞! 但是真正实现时,才发现没有想象中的那么简单...

1. 结构体实现

EventEvent
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}

那如果反序列化呢?

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
}
ClickEventEventEventClickEventClickEventEvent

需要注意的是,以上程序报的错误是

invalid type assertion: tmpEvent.(*ClickEvent) (non-interface type Event on left)
interface{}var tmpEvent interface{}
panic: interface conversion: interface {} is map[string]interface {}, not *main.ClickEvent
map[string]interface {}
unmarshalEventEvent
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)
}
Event*ClickEvent*Event

这里有两个问题:

EventEvent
unmarshalEventinterface{}

第一个问题我们暂时先放一放;对于第二个问题,我们能否使用接口来解决?

3. 使用接口实现扩展

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",
    })
}
EventGetEventTypeProcessevent_typeClickEventDragEventMarshalJSON

使用接口的方式实现的各个事件定义的序列化的使用方式和之前没有什么不同,这里直接看反序列化:

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))
}
unmarshalEventinterfaceEventEventmap[string]interface{}
event.GetEventType()Processinterface{}

4. 支持自定义的事件

到目前为止看来问题几乎已经完全得到了解决,除了上一章提到的一个问题:

EventEvent
unmarshalEvent
unmarshalEvent
event_typeunmarshalEventevent_typeevent_type
unmarshalEventevent_typeevent_typeunmarshalEvent
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
Process()Cancel()Event
type Event interface {
    GetEventType() string
    Process()
    Cancel()
}
Canceler
type Canceler interface {
    Cancel()
}
CancelerunmarshalEvent()EventEvent
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. 自定义反序列化

unmarshalEventEvent
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
PanelEventEventPanelEvent
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++的面向对象实现原理,会有一种“啊,这一块的实现方式我之前也有写过”的奇妙感觉。