iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 27
1
自我挑戰組

Let's Eat GO ! 實務開發雜談by Golang系列 第 27

Day27 .[心得與討論篇] struct 設計解析 - 以melody package (7)

Day27 .[心得與討論篇] struct 設計解析 - 以melody package (7)

今天要分享從melody身上學到的許多概念當中,最喜歡的event trigger,筆者在實務開發上獲得很大幫助。

其實要講的部分,包含了許多概念相輔相成做結合,據我所知的名詞大概如下:

  1. lazy initialization
  2. call back / delegate(C#)
  3. event trigger
  4. closure

1. method拉到struct裡面,只先當變數,並且宣告屬於function型態

type handleMessageFunc func(*Session, []byte)
type handleErrorFunc func(*Session, error)
type handleCloseFunc func(*Session, int, string) error
type handleSessionFunc func(*Session)
type filterFunc func(*Session) bool

// Melody implements a websocket manager.
type Melody struct {
	Config                   *Config
	Upgrader                 *websocket.Upgrader
	messageHandler           handleMessageFunc
	messageHandlerBinary     handleMessageFunc
	messageSentHandler       handleMessageFunc
	messageSentHandlerBinary handleMessageFunc
	errorHandler             handleErrorFunc
	closeHandler             handleCloseFunc
	connectHandler           handleSessionFunc
	disconnectHandler        handleSessionFunc
	pongHandler              handleSessionFunc
	hub                      *hub
}

我們以其中的messageHandler來說明。

messageHandler是一個method,但這個method為什麼不擺在外面呢,而是放在變數裡面呢?

為什麼不寫成如下面這樣?

func (m *Melody) messageHandler(*Session, []byte) {
    ...
}

那是因為它並不打算一開始就決定這個method內容是處理了什麼,或者是不在乎內容是做了什麼,到時候交由別人制定。

再說function型態的zero value是nil,所以輕鬆沒負擔。

當然一開始就給它一個空空如也的function實體,也是可以,避免開發者未先判斷nil直接使用,可能發生panic。

到時候重新assign 真正想要的method,替換掉即可。

return &Melody{
    Config:                   newConfig(),
    Upgrader:                 upgrader,
    messageHandler:           func(*Session, []byte) {},
    messageHandlerBinary:     func(*Session, []byte) {},
    messageSentHandler:       func(*Session, []byte) {},
    messageSentHandlerBinary: func(*Session, []byte) {},
    errorHandler:             func(*Session, error) {},
    closeHandler:             nil,
    connectHandler:           func(*Session) {},
    disconnectHandler:        func(*Session) {},
    pongHandler:              func(*Session) {},
    hub:                      hub,
}

2. 準備外部可以assign真正method的接口,什麼時候做assign外部自己決定

// HandleMessage fires fn when a text message comes in.
func (m *Melody) HandleMessage(fn func(*Session, []byte)) {
	m.messageHandler = fn
}

melody再額外提供method,如上面的HandleMessage,以大寫開頭,提供傳入function型態的實體,然後將它assign到當初宣告的變數。

這種寫法在source code 隨處可見,尤其是經典的http server handler,決定什麼時機用什麼handler。

再把實體的method派上去,好用又彈性,而且method的input和output已經先固定。

3. 決定業務邏輯什麼時候會產生event,然後執行到method

如前篇的readPump和writePump,身處在某個出不去的迴圈當中(業務持續執行中),要怎麼把訊息傳到外面做溝通或處理?

如果用channel,會有個問題,那是送訊息和接訊息的雙方,要先講好channel的型態和格式,那幾乎是不可能的事情。

再者如果是跨package,外部要知道這個送出來的訊息,該怎麼辦?不可能事先知道外面銜接的method長什麼樣子,好讓我們做呼叫。

我們這個package若是提供給外部做import使用,不可能還import對方的package,然而不import,就不知道method什麼樣子。

在readPump()裡面,for迴圈內部會一直卡在s.conn.ReadMessage(),直到從這個ReadMessage()讀取到訊息,則繼續往下執行。

for {
    t, message, err := s.conn.ReadMessage()

    if err != nil {
        s.melody.errorHandler(s, err)
        break
    }

    if t == websocket.TextMessage {
        s.melody.messageHandler(s, message)
    }

    if t == websocket.BinaryMessage {
        s.melody.messageHandlerBinary(s, message)
    }
}

讀取到client送來的資訊,這是一個『事件』,這個事情發生了,才需要通知,而且會主動通知,並且把內容送到該通知的對象。

而處理主動送通知的method,是melody的messageHandler()。

messageHandler()在之前,被透過HandleMessage()做assign了什麼實體不知道,但內容和實際怎麼處理就交給了那個實體。

記得melody介紹系列的第一篇,我們其實就是從掛載,melody提供給外部使用的method開始了分析之旅。

Day21 .[心得與討論篇] struct 設計解析 - 以melody package (1)

// HandleWSReadMsg 掛載websocket收到client message的處理
func HandleWSReadMsg() {
	// 接收client 傳來的訊息
	Serverset.WSServer.HandleMessage(func(s *melody.Session, msg []byte) {
		Entry(s, string(msg))
	})
}

我自己的程式有個HandleWSReadMsg(),裡面使用了melody提供的HandleMessage()。

HandleMessage(func(s *melody.Session, msg []byte) {
		Entry(s, string(msg))
	})

一旦client端有送訊息過來,會跑來這個method。

根據Session的結構我可以知道是哪條連線,哪位使用者,以此作後續的處理和作業。

類似的應用,大部分的server framework也是這樣,如gin、echo,掛載了api路徑。

http request送過來,輾轉跑到指定的method做執行。

maybe再稍微研究一下,各位就可以寫一個自己的server framework套件了(誤)。

感謝大家的耐心閱讀,筆者一些對於struct設計的想法,以及melody的理解分享,就在此告一段落。


上一篇
Day26 .[心得與討論篇] struct 設計解析 - 以melody package (6)
下一篇
Day28 .[心得與討論篇] embedded 嵌入
系列文
Let's Eat GO ! 實務開發雜談by Golang30

尚未有邦友留言

立即登入留言