一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

腳本之家,腳本語言編程技術及教程分享平臺!
分類導航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|

服務器之家 - 腳本之家 - Lua - 分析Lua觀察者模式最佳實踐之構建事件分發(fā)系統(tǒng)

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

2021-11-13 11:02iwiniwin Lua

當對象間存在一對多關系時,則使用觀察者模式(Observer Pattern)。比如,當一個對象被修改時,則會自動通知依賴它的對象。觀察者模式屬于行為型模式

一、前言

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

但這樣就帶來了許多問題:

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

為了解決上面的問題,我們自然想到了觀察者模式

二、觀察者模式

這里簡單說一下什么是觀察者模式:定義對象之間的一對多依賴,這樣一來,當一個對象改變狀態(tài)時,它的所有依賴者(稱之為觀察者)都會接收到通知并自動更新。

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

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

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

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

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

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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ā)金幣修改事件,當前金幣為10
EventSystem:emit(Event.MoneyChanged, 10)

接下來會仔細解讀一下這個EventSystem事件分發(fā)系統(tǒng)的Lua實現(xiàn)代碼。

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

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

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

五、注冊監(jiān)聽事件接口

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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ù)表示要注冊監(jiān)聽的事件名稱,func參數(shù)表示當事件發(fā)生時要觸發(fā)的回調(diào)函數(shù),params表示額外參數(shù),可以設置注冊監(jiān)聽的目標target(可以利用它反注冊所有與其相關的監(jiān)聽),也可以設置要注冊監(jiān)聽的優(yōu)先級,優(yōu)先級越高的越先執(zhí)行。

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

?
1
2
3
4
5
6
7
8
9
10
11
12
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時才會進行排序,listener.emit_count == 0表示的是當前的事件沒有處于派發(fā)狀態(tài),后面講到派發(fā)接口時會詳細解釋,這里讀者只需要知道其表示的含義即可。

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

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

六、反注冊事件監(jiān)聽接口

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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ā)過程中只進行標記刪除
                cb.need_remove = true
                event_listener.need_clean = true
            else
                table.remove(event_listener.list, i)
            end
            break;
        end
    end
end

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function EventSystem:emit( event, ... )
    --- ...
    local event_listener = self._listeners[event]
    local interrupt = false
    local length = #event_listener.list
    -- 這里不能使用ipairs,確保不會觸發(fā)在派發(fā)過程中注冊的事件
    -- 只取當前已經(jīng)注冊的事件數(shù)量,如果在派發(fā)過程中再注冊(調(diào)用了table.insert),本次派發(fā)也不會調(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ā)一個事件,順序執(zhí)行event_listener中注冊的回調(diào)。事件的派發(fā)支持中斷,當執(zhí)行某個回調(diào)時,如果這個回調(diào)返回了true則可以中斷當前事件的派發(fā)。

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

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

八、更多

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

以上就是分析Lua觀察者模式最佳實踐之構建事件分發(fā)系統(tǒng)的詳細內(nèi)容,更多關于Lua 觀察者模式 構建事件分發(fā)系統(tǒng)的資料請關注服務器之家其它相關文章!

原文鏈接:https://www.cnblogs.com/iwiniwin/p/12624090.html

延伸 · 閱讀

精彩推薦
  • LuaLua中table庫函數(shù)方法介紹

    Lua中table庫函數(shù)方法介紹

    這篇文章主要介紹了Lua中table庫函數(shù)方法介紹,本文講解了concat、insert、maxn、remove、sort、foreachi等方法,需要的朋友可以參考下 ...

    腳本之家2502020-04-17
  • LuaLua和C語言的交互詳解

    Lua和C語言的交互詳解

    這篇文章主要介紹了Lua和C語言的交互詳解,Lua和C語言通過棧完成交互,本文結合代碼實例詳細講解了交互的方法,需要的朋友可以參考下 ...

    果凍想3702020-04-14
  • LuaLua教程(二):基礎知識、類型與值介紹

    Lua教程(二):基礎知識、類型與值介紹

    這篇文章主要介紹了Lua教程(二):基礎知識、類型與值介紹,本文講解了Hello World程序、代碼規(guī)范、全局變量、類型與值等內(nèi)容,需要的朋友可以參考下 ...

    腳本之家5922020-04-28
  • LuaLua中計算、執(zhí)行字符串中Lua代碼的方法

    Lua中計算、執(zhí)行字符串中Lua代碼的方法

    這篇文章主要介紹了Lua中計算、執(zhí)行字符串中Lua代碼的方法,類似JavaScript中eval函數(shù)的功能,在Lua中也可以實現(xiàn),需要的朋友可以參考下 ...

    腳本之家6322020-04-30
  • LuaLua簡介、編譯安裝教程及變量等語法介紹

    Lua簡介、編譯安裝教程及變量等語法介紹

    這篇文章主要介紹了Lua簡介、編譯安裝教程及變量等語法介紹,本文同時講解了lua注釋語法、Lua命令行方式等內(nèi)容,需要的朋友可以參考下 ...

    junjie3632020-04-14
  • LuaLua中的元方法__newindex詳解

    Lua中的元方法__newindex詳解

    這篇文章主要介紹了Lua中的元方法__newindex詳解,本文講解了查詢與更新、監(jiān)控賦值、通過table給另一個table賦值等內(nèi)容,需要的朋友可以參考下 ...

    笨木頭8872020-04-09
  • LuaLua實現(xiàn)__add方法重載示例

    Lua實現(xiàn)__add方法重載示例

    這篇文章主要介紹了Lua實現(xiàn)__add方法重載示例,本文直接給出實現(xiàn)代碼,需要的朋友可以參考下 ...

    腳本之家7452020-04-24
  • Lua深入探究Lua中的解析表達式

    深入探究Lua中的解析表達式

    這篇文章主要介紹了深入探究Lua中的解析表達式,對于其語法部分的說明和示例都超詳細,極力推薦此文!需要的朋友可以參考下 ...

    腳本之家3542020-05-05
主站蜘蛛池模板: 国产精品视频2020 | 午夜爱情动作片P | 国产免费看黄的私人影院 | 午夜欧美精品 | 情缘1完整版在线观看 | 2015小明台湾永久区域免费 | 久久免费看少妇高潮A片2012 | 日韩亚洲人成在线综合 | 从后面撕开老师的丝袜动态图 | 国产精品成人扳一级aa毛片 | 亚洲AV无码国产精品色午夜情 | 色综合色狠狠天天久久婷婷基地 | 国产免费小视频在线观看 | 国产剧情一区二区三区 | 日本欧美强乱视频在线 | 日本一道本视频 | 国产在线视频第一页 | 日韩 国产 欧美 | 国产麻豆剧果冻传媒观看免费视频 | 亚洲www视频 | 亚洲欧美日韩精品 | 春意影院午夜爽爽爽免费 | 四虎一影院区永久精品 | 第一福利在线导航 | 香蕉视频在线观看网站 | 超碰97| 亚洲国产欧美在线成人aaaa | 变形金刚第一部 | 毛片手机在线视频免费观看 | 亚洲久草在线 | 欧美性xxxxx| 日韩在线视频免费观看 | 国产精品啪啪 | 日韩性大片免费 | 男生和女生艹逼 | 色吧| caoporn人人| 精品亚洲456在线播放 | a级在线看 | 深夜激情网 | 色姑娘久 |