您好,登錄后才能下訂單哦!
這篇文章主要介紹“Lua觀察者模式和事件分發(fā)系統(tǒng)有什么用”,在日常操作中,相信很多人在Lua觀察者模式和事件分發(fā)系統(tǒng)有什么用問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Lua觀察者模式和事件分發(fā)系統(tǒng)有什么用”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
一、前言
二、觀察者模式
三、事件分發(fā)系統(tǒng)
四、使用事件分發(fā)系統(tǒng)解決問題
五、注冊監(jiān)聽事件接口
六、反注冊事件監(jiān)聽接口
七、事件派發(fā)接口
八、更多
試想這樣一個問題,當某個事件發(fā)生時,比如在游戲中A模塊修改了用戶的金幣數,而B模塊和C模塊提供的功能都依賴于用戶的金幣數,那么,A模塊在修改金幣數的同時,就需要通知B模塊和C模塊。常規(guī)的方法就是A模塊持有B模塊和C模塊的對象,然后分別通過調用對象接口的方式告訴它們,“嘿,我修改了用戶的金幣數,改成了10金幣”。
但這樣就帶來了許多問題:
A模塊引用了B模塊和C模塊,耦合嚴重
A模塊修改金幣數的方法中調用了B,C模塊的方法,當這兩個模塊發(fā)生變化時(比如B模塊接收金幣數的接口名稱改變了,或是C模塊不再需要知道金幣數改變了),A模塊也要修改
當又出現一個D模塊也需要知道金幣數的變化時,同樣需要修改A模塊以適應這種需求
為了解決上面的問題,我們自然想到了觀察者模式。
這里簡單說一下什么是觀察者模式:定義對象之間的一對多依賴,這樣一來,當一個對象改變狀態(tài)時,它的所有依賴者(稱之為觀察者)都會接收到通知并自動更新。
觀察者模式的好處是,對象之間是松耦合的,當一個對象改變狀態(tài)時,它并不需要知道自己的觀察者是誰,只需要發(fā)布通知即可。任何時候都可以增加或刪除觀察者,不會影響到發(fā)布通知的對象。而事件分發(fā)系統(tǒng)就是觀察者模式的一個具體實現
事件分發(fā)系統(tǒng)核心需要提供的功能主要包括以下幾個部分:
當一個對象發(fā)生改變時,可以認為此時產生了一個事件,提供一個派發(fā)事件的接口,以通知所有的觀察者
需要提供注冊監(jiān)聽事件的接口,以讓觀察者可以訂閱自己需要接收的事件
還需提供反注冊監(jiān)聽事件接口,以讓觀察者可以取消自己的訂閱
最好還能在訂閱的時候設置優(yōu)先級,優(yōu)先級越高的可以越先被通知
首先,來看看使用事件分發(fā)系統(tǒng)處理上面提到的問題,會是什么樣的效果。
A模塊只需要派發(fā)金幣修改事件,B,C模塊只需要訂閱金幣修改事件,之后便可以收到通知了。是不是很簡單呢
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實現代碼。
實現事件分發(fā)系統(tǒng)時,需要小心一些特殊情況,比如有以下幾個坑,讀者可以留意一下代碼中對這幾個坑的處理
在事件派發(fā)的過程中訂閱該事件,訂閱還有優(yōu)先級,需要小心處理排序問題
在事件派發(fā)的過程中取消訂閱該事件,需要采用標記移除,不能直接移除
在事件派發(fā)的過程中又派發(fā)了該事件,如何確定事件派發(fā)完成
為了便于講解,下面的代碼省略了一些非關鍵性的代碼,用--- ...
代替。
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
參數表示要注冊監(jiān)聽的事件名稱,func
參數表示當事件發(fā)生時要觸發(fā)的回調函數,params
表示額外參數,可以設置注冊監(jiān)聽的目標target
(可以利用它反注冊所有與其相關的監(jiān)聽),也可以設置要注冊監(jiān)聽的優(yōu)先級,優(yōu)先級越高的越先執(zhí)行。
on
方法的實現還是比較簡單的,主要就是將注冊的相關信息插入到event_listener
表中,但是明明注冊的監(jiān)聽是有優(yōu)先級的,卻仍然只是調用table.insert
將信息插入到表的末尾,這是為什么呢?讀者可以先留意一下,后面會有詳細解釋。
還需要格外注意的是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
時才會進行排序,listener.emit_count == 0
表示的是當前的事件沒有處于派發(fā)狀態(tài),后面講到派發(fā)接口時會詳細解釋,這里讀者只需要知道其表示的含義即可。
事件處于派發(fā)狀態(tài)時不能進行優(yōu)先級排序原因是可能會造成回調的重復觸發(fā)。
比如當前事件有4個回調 a, b, c, d,派發(fā)事件是順序執(zhí)行回調,當執(zhí)行到第3個回調c時,如果在c回調中又注冊了一個優(yōu)先級最高的回調e,立刻排序的話,e插入到第一位,c會被擠到第4位,順序執(zhí)行到第4個回調時,導致c又被調用一次。
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ā)過程中時,直接調用table.remove
移除注冊信息即可,但當事件處于派發(fā)過程中時,不能直接移除,只能先進行標記。
在事件處于派發(fā)過程中時不能直接移除的原因是可能導致遺漏觸發(fā)某些回調,比如當前事件有5個回調 a, b, c, d, e,順序執(zhí)行到第3個回調c時,如果在c回調中調用了off
方法取消自己的監(jiān)聽,此時直接移除c的話,會導致d回調移動到第3位,e移動到第4位,順序執(zhí)行到第4個回調時,調用的是e而遺漏了d。
function EventSystem:emit( event, ... ) --- ... local event_listener = self._listeners[event] local interrupt = false local length = #event_listener.list -- 這里不能使用ipairs,確保不會觸發(fā)在派發(fā)過程中注冊的事件 -- 只取當前已經注冊的事件數量,如果在派發(fā)過程中再注冊(調用了table.insert),本次派發(fā)也不會調用 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
中注冊的回調。事件的派發(fā)支持中斷,當執(zhí)行某個回調時,如果這個回調返回了true
則可以中斷當前事件的派發(fā)。
值得一提的是,代碼通過對應的event_listener.emit_count = event_listener.emit_count + 1
和event_listener.emit_count = event_listener.emit_count - 1
來記錄事件的派發(fā)狀態(tài),當emit_count > 0
則表明事件還在派發(fā)過程中。當emit_count == 0
則表明事件派發(fā)完成。
不能使用event_listener.is_emiting = true
和event_listener.is_emiting = false
代替的原因是如果在觸發(fā)的回調中又派發(fā)了事件,形成了遞歸,那么二次派發(fā)事件結束時會直接將event_listener.is_emiting
置為flase
,導致一次派發(fā)事件對應的派發(fā)狀態(tài)被標記錯誤
事件分發(fā)系統(tǒng)的完整源碼可以點擊這里查看,測試用例可以點擊這里查看
更多Lua相關的設計與使用,比如面向對象(代碼中用到的class關鍵字),組件系統(tǒng),分模塊加載等等,可以查看GitHub倉庫LuaKit
到此,關于“Lua觀察者模式和事件分發(fā)系統(tǒng)有什么用”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續(xù)學習更多相關知識,請繼續(xù)關注億速云網站,小編會繼續(xù)努力為大家?guī)砀鄬嵱玫奈恼拢?/p>
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。