观察者模式与依赖反转原则

最近写了一个“休息吧,程序员”的小工具,用到了观察者模式,感觉自己的理解还不够深入,借此机会稍微深入学习一下。

项目介绍:提醒程序员不要过度工作的一个脚本,暖心程序跟你道晚安,使用观察者模式实现,我把它叫做:休息吧,程序员!

po图

项目地址:https://github.com/cooljacket/relax_please
先po一下我实现出来的效果图(注意桌面右上角):
观察者模式与依赖反转原则_第1张图片
暖心跟你道晚安~

观察者模式与依赖反转原则_第2张图片
如老妈一般的监督你不要过劳!

项目设计

首先说下这个项目的组成:
1. 监听器:监听系统的键盘输入、系统时间
2. 发送提醒:一旦监听的事件发生(比如超过11点,该睡觉了),那么监听器就会触发这个“发送提醒器”,提醒器会以某种方式发送通知给使用者

这样看来,这个项目的对象关系是一个很经典的“观察者模式”的模型,下面先看一下我画的UML图:
观察者模式与依赖反转原则_第3张图片

看不懂UML图的朋友也没关系,稍微解释一下就知道了:
1. 监听器抽象接口Listener,它实现了所有监听器共有的功能,即绑定观察者,解绑观察者,(在所监听的事件发生的时候)通知所有绑定的观察者,以及监听事件。
2. 我需要监听两个事件,一个是使用电脑的情况(通过键盘输入来估计),一个是当前的工作时间(看看是否有熬夜),所以继承实现了两个具体的监听器,KeyBoardListener和SayGoodNightListener,它们都只需要实现listening函数即可,因为其它的都在抽象类Listener中实现好了!
3. 观察者抽象类Observer,这个其实只是个接口而已,不是一个类,因为update()虽然是共有的接口函数,但它的实体是因人而异的,没法在这个类里给出一个default的实现(对比一下Listener的notify方法就知道了)。
4. 目前的发通知给使用者,只实现了一种方式:发送泡泡弹窗到桌面右上角,所以只写了一个NotifySendObserver类。

值得注意的是:一个监听器可以有多个观察者,而一般一个观察者只能有一个监听器。比如监听是否熬夜这个事件,可以有多种通知方式,比如响闹铃,发送泡泡弹窗到桌面的右上角,记录熬夜情况到日志文件以备后续分析,等等。而一般一个观察者都是特化了的,不会用在多个监听器上面,比如你熬夜的时候响一段“睡吧睡吧我亲爱滴宝贝……”的摇篮曲,是针对特定的这个(熬夜)事件的!

什么是观察者模式?

引用一段话1来描述:

建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应做出反应。在此,发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展,这就是观察者模式的模式动机。

注意这里,观察者并不是真的去观察,而是被观察目标通知才会得知有事件发生的!

这是一个事件驱动模型,不直接想它的好处,先反过来想,如果不这样做,而是让NotifySendObserver主动去询问/检测事件有无发生,这样的话,得设置好去检测的时间间隔,除非不停去检测,否则肯定是有概率会错过一些事件的。忙死忙活,吃力不讨好^_^(手动微笑)。

而观察者模式正是把检测事件和响应事件这两个职责给分离了,各司其职。我这边自己监视使用者有没有熬夜,有的话,我就通过接口告诉你,你再发通知给使用者,叫ta赶紧睡觉。没有发通知给你,你也不用来问我,就是没事情发生,各自清闲~

再举个简单的例子,就是我们平时按手机app里的按钮,那个应用内部肯定不会时刻去检测这个按钮有没有被按下,而是按钮被按下的时候,系统会发送一个消息给应用去处理。

Why抽象接口?

有人可能会说,哎,你怎么要搞这么麻烦啊,无论是监听器,还是观察者,都要抽象出一个接口出来?

这里安利一个我自己到现在还没懂透的“面向对象五大原则SOLID”:
观察者模式与依赖反转原则_第4张图片
参考维基百科:https://zh.wikipedia.org/wiki/SOLID_(%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1%E8%AE%BE%E8%AE%A1)

这里很明显运用的就是D,依赖反转原则。

问题还是回来了,用了有什么好处呢?不用,又有什么坏处呢?

这里推荐一篇关于依赖倒转原则的好文章,里边的“妈妈讲故事”的例子很鲜明地体现了这个原则的好处:高层模块可以自由选择底层模块,耦合性很低。

说人话,就是,在这个例子中,如果我不抽象一个Observer接口的话,那么Listener(高层模块)就直接依赖NotifySendObserver(底层模块)了,那如果我后续不想用这种通知方式,而是想改用音乐作为闹铃,是不是除了写多一个MusicObserver类,还要改动Listener类呢?

有人可能会说,这在python里都一样,因为每次运行都是重新编译,那试问一下,如果你是公开发布的一个东西,还能这么做吗?如果不是用python而是C++、Java之类的静态语言呢?

嗯,抽象接口的好处就是这样了。

而Listener作为一个抽象类,则不是同样的想法了。我想实现多个监听器类,它们有很鲜明的公共逻辑,比如上面的KeyBoardListener和SayGoodNightListener,那这个时候主要是继承重用的想法,而不是为了要依赖反转。


  1. http://design-patterns.readthedocs.io/zh_CN/latest/behavioral_patterns/observer.html ↩

你可能感兴趣的:(设计模式,瞎捣鼓)