浅谈对象之间通信的解决方案——Event机制

  欢迎参与讨论,转载请注明出处。
  本文转载自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本身也是有性能代价以及构建成本的,不可能面面俱到。在某些你认为必要且可掌控的情况下,直传对象也并非不可以。毕竟解耦的目的也是为了提高可维护性,只要你觉得这样做是可以接受的,那么便可以了。

后记

  以上内容仅为提供一个思路,它或许并非最完善的,也不一定能适用于所有编程语言中,对此有所心得者,欢迎前来探讨,提供更佳的思路。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值