观察者模式、事件通知、消息队列三者区别
观察者模式、事件通知、消息队列三者有类似,都有回调函数注册,通知调用的设计,容易混淆。
简述和区别
- 观察者模式:被观察对象状态改变,所有观察它的对象得到通知。也称订阅模式,英文Observer。
被观察者不依赖观察者,通过依赖注入达到控制反转。 - 事件通知:事件发生后,通知所有关心这个事件的对象。
与观察者模式对比,可理解成所有对象都只依赖事件系统。一半对象观察事件系统,等待特定通知;一半对象状态变化就通过事件系统发出事件。
观察者也不依赖被观察对象,他只关心事件,不需要到被观察对象那儿注册自己。
被观察者也只是普通对象,状态改变,通过事件系统发出事件就行了。 - 消息队列:将消息排成队列,逐步分发通知。
与事件通知对比,可理解成事件不是立即通知,而是保存到队列里,稍后通知。
这个可以达到时间解耦的效果。Windows的消息循环就是一个应用。多线程情况下,消息队列优先于事件系统。
观察者模式
以上课铃声为例子。上课铃声响,同学们回教室。
1. 简单写法
1 2 3 4 5 6 7 | class 上课铃{ function 响() for 学生 in 学生们 do 学生->回教室() end end } |
这样写有问题:
- 上课铃主动通知学生回教室,依赖关系反了。
- 上课铃响,老师要来上课,这个也得上课铃通知,上课铃管的东西太多了。
2. 轮询
1 2 3 4 5 6 7 | class 学生{ function update() if 上课玲响 then 回教室() end end } |
这样上课铃只管按时响就行了,也有问题:
- 学生的update会越来越复杂,学生还有很多其他事情要做呢。
- update太耗时了,学生们,要精神紧张地仔细停玲声有没有响起。
3. 用观察者模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class 上课铃: Subject{ function 响() NotifyObservers() end } class 学生: Observer{ function init() 上课玲->AddObserver( this .回教室) end function 回教室() ... end function un_init() 上课玲->RemoveObserver( this .回教室) end } |
这样,上课铃只要响的时候发个通知,学生们就等通知好了。老师也类似,等通知就行了。
小结
实际就是注册个回调函数,完美的将观察对象和被观察对象分离。
个人理解:依赖注入,控制反转。观察者依赖被观察者,而不是被观察者依赖观察者。
事件系统
观察者模式有两个问题:
- 观察者要获得被观察对象,然后才能注册。
有时只是要知道某个事件发生了而已,类似网络初始化好了的事件,并不需要获得网络管理对象。 - 观察者和被观察者要继承对象的,在单继承体系里,这是很昂贵的一件事。
上课铃的例子里,学生只关心铃声,不关心上课铃这个物体。
用事件模式就可以换个写法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | class 事件系统{ function register(事件类型, handle); function remove(事件类型, handle); function trigger(事件类型, 数据); } class 上课铃{ function 响() 事件系统->trigger( "上课铃声" ) end } class 学生{ function init() 事件系统->register( "上课铃声" , this ->回教室) end function 回教室() ... end function un_init() 事件系统->remove( "上课铃声" , this .回教室) end } |
小结
事件通知系统用的很广泛的。很多代码会有个EventDispatcher
、EventControl
之类的类。
特别是UI程序,当数据发生变化时通知相关UI更新。
观察者模式可以做到,但是事件通知来实现会更加简单。
消息队列
消息队列和事件系统很像。但是消息队列不是立即通知,而是把消息先放到队列里再通知。
上课铃的例子
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 27 28 | class 消息队列{ function register(消息类型, handle); function remove(消息类型, handle); function sendMsg(消息); function process(); } class 上课铃{ function 响() 消息队列->sendMsg( "上课铃声" ) end } class 学生{ function init() 消息队列->register( "上课铃声" , this ->回教室) end function 回教室() ... end function un_init() 消息队列->remove( "上课铃声" , this .回教室) end } main{ while (有消息) do 消息队列->process() end } |
从伪代码也可以看出,消息队列和事件系统的使用基本是一样的。如果消息队列不延后处理,就是事件系统了。
消息队列可以用于多线程,接受处理消息的handle们在主线程里。发送消息的可以在其他线程里。
简单总结
需要分层解耦就用事件通知系统。
需要时间解耦就用消息队列。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步