佳木斯湛栽影视文化发展公司

主頁(yè) > 知識(shí)庫(kù) > 分析Lua觀察者模式最佳實(shí)踐之構(gòu)建事件分發(fā)系統(tǒng)

分析Lua觀察者模式最佳實(shí)踐之構(gòu)建事件分發(fā)系統(tǒng)

熱門(mén)標(biāo)簽:百度競(jìng)價(jià)排名 網(wǎng)站排名優(yōu)化 Linux服務(wù)器 地方門(mén)戶網(wǎng)站 呼叫中心市場(chǎng)需求 AI電銷 服務(wù)外包 鐵路電話系統(tǒng)

一、前言

試想這樣一個(gè)問(wèn)題,當(dāng)某個(gè)事件發(fā)生時(shí),比如在游戲中A模塊修改了用戶的金幣數(shù),而B(niǎo)模塊和C模塊提供的功能都依賴于用戶的金幣數(shù),那么,A模塊在修改金幣數(shù)的同時(shí),就需要通知B模塊和C模塊。常規(guī)的方法就是A模塊持有B模塊和C模塊的對(duì)象,然后分別通過(guò)調(diào)用對(duì)象接口的方式告訴它們,“嘿,我修改了用戶的金幣數(shù),改成了10金幣”。

但這樣就帶來(lái)了許多問(wèn)題:

  • A模塊引用了B模塊和C模塊,耦合嚴(yán)重
  • A模塊修改金幣數(shù)的方法中調(diào)用了B,C模塊的方法,當(dāng)這兩個(gè)模塊發(fā)生變化時(shí)(比如B模塊接收金幣數(shù)的接口名稱改變了,或是C模塊不再需要知道金幣數(shù)改變了),A模塊也要修改
  • 當(dāng)又出現(xiàn)一個(gè)D模塊也需要知道金幣數(shù)的變化時(shí),同樣需要修改A模塊以適應(yīng)這種需求

為了解決上面的問(wèn)題,我們自然想到了觀察者模式。

二、觀察者模式

這里簡(jiǎn)單說(shuō)一下什么是觀察者模式:定義對(duì)象之間的一對(duì)多依賴,這樣一來(lái),當(dāng)一個(gè)對(duì)象改變狀態(tài)時(shí),它的所有依賴者(稱之為觀察者)都會(huì)接收到通知并自動(dòng)更新。

觀察者模式的好處是,對(duì)象之間是松耦合的,當(dāng)一個(gè)對(duì)象改變狀態(tài)時(shí),它并不需要知道自己的觀察者是誰(shuí),只需要發(fā)布通知即可。任何時(shí)候都可以增加或刪除觀察者,不會(huì)影響到發(fā)布通知的對(duì)象。而事件分發(fā)系統(tǒng)就是觀察者模式的一個(gè)具體實(shí)現(xiàn)

三、事件分發(fā)系統(tǒng)

事件分發(fā)系統(tǒng)核心需要提供的功能主要包括以下幾個(gè)部分:

  • 當(dāng)一個(gè)對(duì)象發(fā)生改變時(shí),可以認(rèn)為此時(shí)產(chǎn)生了一個(gè)事件,提供一個(gè)派發(fā)事件的接口,以通知所有的觀察者
  • 需要提供注冊(cè)監(jiān)聽(tīng)事件的接口,以讓觀察者可以訂閱自己需要接收的事件
  • 還需提供反注冊(cè)監(jiān)聽(tīng)事件接口,以讓觀察者可以取消自己的訂閱
  • 最好還能在訂閱的時(shí)候設(shè)置優(yōu)先級(jí),優(yōu)先級(jí)越高的可以越先被通知

四、使用事件分發(fā)系統(tǒng)解決問(wèn)題

首先,來(lái)看看使用事件分發(fā)系統(tǒng)處理上面提到的問(wèn)題,會(huì)是什么樣的效果。

A模塊只需要派發(fā)金幣修改事件,B,C模塊只需要訂閱金幣修改事件,之后便可以收到通知了。是不是很簡(jiǎn)單呢

local B = class()
function B:on_money_change( money )
    print(money, "B receive event")
end
-- 訂閱金幣修改事件
EventSystem:on(Event.MoneyChanged, B.on_money_change, {target = B})

local C = class()
function C:on_money_change( money )
    print(money, "C receive event")
end
EventSystem:on(Event.MoneyChanged, C.on_money_change, {target = C})
-- 在A模塊中派發(fā)金幣修改事件,當(dāng)前金幣為10
EventSystem:emit(Event.MoneyChanged, 10)

接下來(lái)會(huì)仔細(xì)解讀一下這個(gè)EventSystem事件分發(fā)系統(tǒng)的Lua實(shí)現(xiàn)代碼。

實(shí)現(xiàn)事件分發(fā)系統(tǒng)時(shí),需要小心一些特殊情況,比如有以下幾個(gè)坑,讀者可以留意一下代碼中對(duì)這幾個(gè)坑的處理

  • 在事件派發(fā)的過(guò)程中訂閱該事件,訂閱還有優(yōu)先級(jí),需要小心處理排序問(wèn)題
  • 在事件派發(fā)的過(guò)程中取消訂閱該事件,需要采用標(biāo)記移除,不能直接移除
  • 在事件派發(fā)的過(guò)程中又派發(fā)了該事件,如何確定事件派發(fā)完成

為了便于講解,下面的代碼省略了一些非關(guān)鍵性的代碼,用--- ...代替。

五、注冊(cè)監(jiān)聽(tīng)事件接口

function EventSystem:on( event, func, params )
    --- ...
    local event_listener = self._listeners[event]
    params = params or {}
    local priority = params.priority or 0
    local target = params.target
    --- ...
    local cb = {target = target, func = func, id = id, priority = priority}
    table.insert(event_listener.list, cb)
    id = id + 1
    if priority > 0 then
        event_listener.need_sort = true
        self:sort(event_listener)
    end
end

on方法中event參數(shù)表示要注冊(cè)監(jiān)聽(tīng)的事件名稱,func參數(shù)表示當(dāng)事件發(fā)生時(shí)要觸發(fā)的回調(diào)函數(shù),params表示額外參數(shù),可以設(shè)置注冊(cè)監(jiān)聽(tīng)的目標(biāo)target(可以利用它反注冊(cè)所有與其相關(guān)的監(jiān)聽(tīng)),也可以設(shè)置要注冊(cè)監(jiān)聽(tīng)的優(yōu)先級(jí),優(yōu)先級(jí)越高的越先執(zhí)行。

on方法的實(shí)現(xiàn)還是比較簡(jiǎn)單的,主要就是將注冊(cè)的相關(guān)信息插入到event_listener表中,但是明明注冊(cè)的監(jiān)聽(tīng)是有優(yōu)先級(jí)的,卻仍然只是調(diào)用table.insert將信息插入到表的末尾,這是為什么呢?讀者可以先留意一下,后面會(huì)有詳細(xì)解釋。
還需要格外注意的是sort方法

function EventSystem:sort( listener )
    if listener.need_sort == true and listener.emit_count == 0 then
        table.sort(listener.list, function ( a, b )
            if a.priority == b.priority then
                return a.id  b.id
            else
                return a.priority > b.priority
            end
        end)
        listener.need_sort = false;
    end
end

可以看到sort方法必須在listener.emit_count == 0時(shí)才會(huì)進(jìn)行排序,listener.emit_count == 0表示的是當(dāng)前的事件沒(méi)有處于派發(fā)狀態(tài),后面講到派發(fā)接口時(shí)會(huì)詳細(xì)解釋,這里讀者只需要知道其表示的含義即可。

事件處于派發(fā)狀態(tài)時(shí)不能進(jìn)行優(yōu)先級(jí)排序原因是可能會(huì)造成回調(diào)的重復(fù)觸發(fā)。

比如當(dāng)前事件有4個(gè)回調(diào) a, b, c, d,派發(fā)事件是順序執(zhí)行回調(diào),當(dāng)執(zhí)行到第3個(gè)回調(diào)c時(shí),如果在c回調(diào)中又注冊(cè)了一個(gè)優(yōu)先級(jí)最高的回調(diào)e,立刻排序的話,e插入到第一位,c會(huì)被擠到第4位,順序執(zhí)行到第4個(gè)回調(diào)時(shí),導(dǎo)致c又被調(diào)用一次。

六、反注冊(cè)事件監(jiān)聽(tīng)接口

function EventSystem:off( event, func, params )
    --- ...
    local event_listener = self._listeners[event]
    params = params or {}
    for i,cb in ipairs(event_listener.list) do
        if cb.func == func and cb.target == params.target then
            if event_listener.emit_count > 0 then
                -- 派發(fā)過(guò)程中只進(jìn)行標(biāo)記刪除
                cb.need_remove = true
                event_listener.need_clean = true
            else
                table.remove(event_listener.list, i)
            end
            break;
        end
    end
end

off方法用于取消事件監(jiān)聽(tīng),當(dāng)事件未處于派發(fā)過(guò)程中時(shí),直接調(diào)用table.remove移除注冊(cè)信息即可,但當(dāng)事件處于派發(fā)過(guò)程中時(shí),不能直接移除,只能先進(jìn)行標(biāo)記。
在事件處于派發(fā)過(guò)程中時(shí)不能直接移除的原因是可能導(dǎo)致遺漏觸發(fā)某些回調(diào),比如當(dāng)前事件有5個(gè)回調(diào) a, b, c, d, e,順序執(zhí)行到第3個(gè)回調(diào)c時(shí),如果在c回調(diào)中調(diào)用了off方法取消自己的監(jiān)聽(tīng),此時(shí)直接移除c的話,會(huì)導(dǎo)致d回調(diào)移動(dòng)到第3位,e移動(dòng)到第4位,順序執(zhí)行到第4個(gè)回調(diào)時(shí),調(diào)用的是e而遺漏了d。

七、事件派發(fā)接口

function EventSystem:emit( event, ... )
    --- ...
    local event_listener = self._listeners[event]
    local interrupt = false
    local length = #event_listener.list
    -- 這里不能使用ipairs,確保不會(huì)觸發(fā)在派發(fā)過(guò)程中注冊(cè)的事件
    -- 只取當(dāng)前已經(jīng)注冊(cè)的事件數(shù)量,如果在派發(fā)過(guò)程中再注冊(cè)(調(diào)用了table.insert),本次派發(fā)也不會(huì)調(diào)用
    for i = 1, length do
        if interrupt == true then
            break
        end
        local cb = event_listener.list[i]
        if cb.func and cb.need_remove ~= true then
            event_listener.emit_count = event_listener.emit_count + 1
            if cb.target then
                interrupt = cb.func(cb.target, ...)
            else
                interrupt = cb.func(...)
            end
            event_listener.emit_count = event_listener.emit_count - 1
        end
    end
    self:sort(event_listener);
    self:clean(event_listener);
    return interrupt
end

emit方法負(fù)責(zé)派發(fā)一個(gè)事件,順序執(zhí)行event_listener中注冊(cè)的回調(diào)。事件的派發(fā)支持中斷,當(dāng)執(zhí)行某個(gè)回調(diào)時(shí),如果這個(gè)回調(diào)返回了true則可以中斷當(dāng)前事件的派發(fā)。

值得一提的是,代碼通過(guò)對(duì)應(yīng)的event_listener.emit_count = event_listener.emit_count + 1event_listener.emit_count = event_listener.emit_count - 1來(lái)記錄事件的派發(fā)狀態(tài),當(dāng)emit_count > 0則表明事件還在派發(fā)過(guò)程中。當(dāng)emit_count == 0則表明事件派發(fā)完成。

不能使用event_listener.is_emiting = trueevent_listener.is_emiting = false代替的原因是如果在觸發(fā)的回調(diào)中又派發(fā)了事件,形成了遞歸,那么二次派發(fā)事件結(jié)束時(shí)會(huì)直接將event_listener.is_emiting置為flase,導(dǎo)致一次派發(fā)事件對(duì)應(yīng)的派發(fā)狀態(tài)被標(biāo)記錯(cuò)誤

八、更多

事件分發(fā)系統(tǒng)的完整源碼可以點(diǎn)擊這里查看,測(cè)試用例可以點(diǎn)擊這里查看
更多Lua相關(guān)的設(shè)計(jì)與使用,比如面向?qū)ο螅ùa中用到的class關(guān)鍵字),組件系統(tǒng),分模塊加載等等,可以查看GitHub倉(cāng)庫(kù)LuaKit

以上就是分析Lua觀察者模式最佳實(shí)踐之構(gòu)建事件分發(fā)系統(tǒng)的詳細(xì)內(nèi)容,更多關(guān)于Lua 觀察者模式 構(gòu)建事件分發(fā)系統(tǒng)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

您可能感興趣的文章:
  • SpringBoot+Redis執(zhí)行l(wèi)ua腳本的方法步驟
  • 如何使用Vim搭建Lua開(kāi)發(fā)環(huán)境詳解
  • Lua中三種循環(huán)語(yǔ)句的使用講解
  • Lua中的變量與賦值方法
  • Android事件分發(fā)機(jī)制(上) ViewGroup的事件分發(fā)
  • 詳解EventDispatcher事件分發(fā)組件
  • Android View 事件分發(fā)機(jī)制詳解
  • PHP中常用的三種設(shè)計(jì)模式詳解【單例模式、工廠模式、觀察者模式】
  • 淺談發(fā)布訂閱模式與觀察者模式

標(biāo)簽:崇左 黃山 仙桃 湘潭 銅川 湖南 蘭州 衡水

巨人網(wǎng)絡(luò)通訊聲明:本文標(biāo)題《分析Lua觀察者模式最佳實(shí)踐之構(gòu)建事件分發(fā)系統(tǒng)》,本文關(guān)鍵詞  ;如發(fā)現(xiàn)本文內(nèi)容存在版權(quán)問(wèn)題,煩請(qǐng)?zhí)峁┫嚓P(guān)信息告之我們,我們將及時(shí)溝通與處理。本站內(nèi)容系統(tǒng)采集于網(wǎng)絡(luò),涉及言論、版權(quán)與本站無(wú)關(guān)。
  • 相關(guān)文章
  • 收縮
    • 微信客服
    • 微信二維碼
    • 電話咨詢

    • 400-1100-266
    武定县| 万载县| 宝鸡市| 敦化市| 揭阳市| 霍城县| 城市| 乌拉特后旗| 德清县| 临西县| 云阳县| 讷河市| 大安市| 宁明县| 鲁山县| 塔城市| 土默特右旗| 东安县| 长春市| 齐河县| 吴堡县| 保山市| 周口市| 施秉县| 谷城县| 湖口县| 尼木县| 始兴县| 隆回县| 托克托县| 枣庄市| 谢通门县| 郑州市| 葫芦岛市| 凯里市| 肇庆市| 西平县| 盐城市| 筠连县| 贵德县| 舞钢市|