歡迎參與討論,轉載請註明出處。
本文轉載自https://musoucrow.github.io/2017/04/19/event_mechanism/
前言
在程序設計的時候,不同對象與模塊間總是不可避免會發生相互調用的情況,如果選擇將對象互相作爲參數傳入給對方使用,那麼這種現象一般被稱爲耦合,這樣實際上就讓兩個部分連在了一塊。當然這樣子實際上並沒有什麼問題,只要這符合你的設計預期。只是一旦開發規模增大、開發人員增多、耦合程度加劇的話,程序的維護成本也便會隨之劇增。往往會出現某個模塊在另一個模塊處被做了一些修改而不自知,以及在腳本語言的情況下,沒有private保護的對象到了他處等同徹底的暴露,隨便的修改的話,封裝性也隨之不存。那麼由此看來,必須擁有一套對象之間通信的解決方案了,本文便是提供了一種思路供給參考。
以下代碼演示將使用Lua語言,接下來的內容對閱讀者的Lua水平有一定的要求。如果你不知道在Lua中class的實現方式,可參考這篇文章。
使用機制之前的做法
首先我們演示一下不用Event機制之前的做法,也就是一般人會用的做法。
-- A.lua
local Class = require("class")
local A = Class()
local B = require("b")
function A:Ctor()
self.value = 1
self.b = B.New(self)
end
return A
-- B.lua
local Class = require("class")
local B = Class()
function B:Ctor(a)
self.a = a
end
function B:Print()
print(self.a.value)
end
return B
使用機制之後的做法
由上可以看出,這種直傳對象的做法其實是十分危險的,它很容易破壞封裝性,並且會達到「你中有我,我中有你」的效果,並且這還是在上下級的情況下。接下來便演示下使用了Event機制後的做法。
-- A.lua
local Class = require("class")
local A = Class()
local B = require("b")
function A:Ctor()
self.value = 1
self.event = function(self, ...) self:OnEvent(...) end
self.b = B.New(self.event)
end
function A:OnEvent(type, ...)
if (type == "GetValue") then
return self.value
end
return "No Event"
end
return A
-- B.lua
local Class = require("class")
local B = Class()
function B:Ctor(upperEvent)
self.upperEvent = upperEvent
end
function B:Print()
print(self.upperEvent("GetValue"))
end
return B
通過對比可以看出,使用了Event機制後,不僅保證了封裝性,而且還隔離了A對象的實現,換句話說,B對象的upperEvent已經不僅限於A了,只要是提供了一致的Event接口即可,並且A對象還能清除的知道自己對外究竟提供了什麼接口,可維護性也隨之提高了。
當然代價還是有的,那便是調用Event時的函數調用次數比傳統方式有所增加,但我認爲這是值得的。當然這只是一種思路,也未嘗不可優化。
疑難解答
問:關於兩個平級對象需要相互調用要怎麼做?
答:參考以下例子:
-- A.lua
local Class = require("class")
local A = Class()
local B = require("b")
local C = require("c")
function A:Ctor()
self.value = 1
self.event = function(self, ...) self:OnEvent(...) end
self.b = B.New(self.event)
self.c = C.New(self.event)
end
function A:OnEvent(type, ...)
if (type == "GetValue") then
return self.b:GetValue()
elseif (type == "SetTag") then
return self.c:SetValue(...)
end
return "No Event"
end
return A
-- B.lua
local Class = require("class")
local B = Class()
function B:Ctor(upperEvent)
self.upperEvent = upperEvent
self.value = 1
self.upperEvent("SetTag", "123")
end
function B:GetValue()
return self.value
end
return B
-- C.lua
local Class = require("class")
local C = Class()
function C:Ctor(upperEvent)
self.upperEvent = upperEvent
self.tag = ""
end
function C:SetTag(tag)
self.tag = tag
end
function C:Print()
print(self.upperEvent("GetValue"))
end
return C
由此可見,平級對象的相互調用,只需要統一由上級管理好需要調用的接口,然後平級對象從上級Event中獲取即可。
問:一個對象可以擁有多個OnEvent()麼?也就是針對不同對象派出不同的Event。
答:一般來說不推薦這麼做,因爲這隻會使得維護成本提升。當然你很清楚自己的需求以及這麼做的代價的話,但試無妨。
問:使用Event機制後有效的保證了封裝性,但是對於上級管理下級的時候並不存在這種封裝性的保護,那麼該怎麼辦呢?
答:這個在腳本語言裏是一個沒辦法的事,原則上最好是不要對外直接暴露變量,只使用函數。哪怕這會帶來更高的性能代價,對於維護性而言也是值得的,當然這個問題或許也可以通過特殊的手段解決(參考Lua元表的內容)。
問:是不是嚴格意義所有情況下都不應該直傳對象而採用Event呢?
答:這樣顯然是不現實的,比如某些類的業務本身就需要獲取到對象本身(如容器),以及Event本身也是有性能代價以及構建成本的,不可能面面俱到。在某些你認爲必要且可掌控的情況下,直傳對象也並非不可以。畢竟解耦的目的也是爲了提高可維護性,只要你覺得這樣做是可以接受的,那麼便可以了。
後記
以上內容僅爲提供一個思路,它或許並非最完善的,也不一定能適用於所有編程語言中,對此有所心得者,歡迎前來探討,提供更佳的思路。