首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

听说你还不会写观察者模式?

作者 | 静幽水

责编 | Elle

问题背景

话说程序员小强成功进入一家公司,并且老板也信守承诺给他分配了一个女朋友小美,老板这样做除了能让小强每天安心写代码之外,还有另外两个意图,第一就是小美是安插在小强身边的眼线,负责监督小强的工作,第二个也是最重要的目的是通过小美可以把公司重要的通知传递给小强。如下是过程示意图

以前我们是怎么用程序演示上面过程呢?(这里还没有使用观察者模式)为了简单,我不使用接口,直接使用老板,程序员,小美和客户端四个类。

老板类如下:

程序员类:

小美类,使用线程进行监控老板类,查看老板是否发出加班通知,如果发出就通知所有程序员加班

继承Thread类,重写run()方法,然后start()就可以启动一个线程了。

客户端

将老板和程序员小强的实例传进小美类中,然后开启线程进行监听老板是否发出通知,只要这时老板发出通知,程序员就可以接收到并执行加班的命令。输出如下:

有何问题

上面这个程序,小美使用死循环来监听,导致CPU飙升,严重浪费了公司的资源,公司面临亏损,老板决定裁员来开源节流,就这样处在程序员和老板直接的小美就被裁掉了,没了小美之后,程序员就要上班时去老板办公室签到,下班时再去询问今晚是否加班。久而久之,程序员心生怨念,老板每天被问来问去也很烦,于是他们想到了一种新的解决方案。

解决方法

于是老板买来几个小喇叭,放在程序员的办公室里,而按钮放在老板的办公室里,如果有新的通知,就按喇叭,程序员们听到之后就前往老板的办公室领取具体的通知内容,这样就不需要程序员每天去老板办公室询问是否有新的通知了,而且还节约了成本,所谓一举两得。

怎么用代码实现上面的过程呢

首先定义一个老板的接口类,里面主要是公共方法如添加程序员对象和删除程序员对象以及通知所有程序员的方法。以后还有其他的小老板,都继承自这个接口:

当前老板的类,当前老板设置一个状态,就是今晚程序员是否需要加班,当这个状态发生改变时,就调用上面的notifyProgrammer()方法通知所有程序员:

程序员的接口,只有一个方法,被通知的是否加班方法:

具体的程序员类,实现上面加班的方法,执行加班行为:

客户端,使用上面的类,先创建一个老板对象和两个程序员对象,并把两个程序员对象添加到老板对象中的程序员列表中(相当于上班前来打卡,老板知道今天有哪些程序员来上班了),然后老板发布今晚加班的通知,所有程序员就会自动接收到通知,并执行加班的行为:

打印结果如下:

模式讲解

以上的实现方式就是通过观察者模式实现的,观察者模式定义:

定义对象间的一种一对多的依赖关系。当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

上面老板和程序员的关系就是典型的 一对多关系,并且程序员是否加班的状态也是由老板决定的,程序员会根据老板的通知执行相对应的行为,而老板不需要知道具体有哪些程序员,只需要维护一个程序员的列表就可以了,通知时遍历这个列表。

老板相当于被观察者,也被称为目标:Subject,程序员是观察者,也就是Observer,一个目标可以有任意多个观察者对象,一旦目标的状态发生变化,所有注册的观察者都会得到通知,然后各个观察者会对通知做出相应的反应。

但是需要注意,观察者始终是处于一种被动的地位,也就是注册和删除都是由目标来决定的,观察者没有权限决定是否观察哪个目标。这叫做单向依赖,观察者依赖于目标,目标是不会依赖于观察者的。这一点也很好了解,就像是程序员没有权限决定听不听老板的通知一样,老板通知所有程序员加班,你不能决定你能不能接收到通知吧。

观察者也可以观察多个目标,但是应该为不同的观察者目标定义不同的回调函数用来区分,也就是每个通知对应一个响应函数。这一点也很容易理解,一家公司会有老板,还会有部门经理,主管等职位,程序员都要听他们的。

观察者模型结构示意图:

Subject目标对象,相当于上面的Boss类

Observer:观察者接口,提供目标通知时对应的更新方法,可以在这个方法中回调目标对象,以获取目标对象的数据,相当于上面的IProgrammer接口

ConcreteSubject:具体的目标实现类,相当于上面的Li_boss

ConcreteObserver:具体的观察者对象,用来接收通知并做出响应,相当于上面的Programmer类。

新的问题

公司这样执行了一段时间,新的问题又出现了,因为老板并不只是通知加班这么简单,还会通知其他的事情,可能每个程序员想得到的通知不一样,比如小强去出差,小华去休假。这个时候该怎么通知他们呢。

这就涉及观察者的两种模型,拉模型和推模型。上面的代码就是推模型实现的。

推模型:目标对象主动向观察者推送目标的详细信息,不管观察者是否需要,推送的信息通常是目标对象的全部或部分数据。如上面就是推送的是否加班的状态。

拉模型“目标对象在通知观察者的时候,只会传递少量信息,如果不知道观察者具体需要什么数据,就把目标对象自身传递给观察者,让观察者自己按需去取信息。

为了方便对比,接下来用拉模型实现通知加班的代码,首先看一下Boss类,只需要更改通知的方法:

notifyProgrammer()方法去掉参数,调用 programmer.work_overtime()时将自身this传递进去。具体的老板类Li_Boss只需要实现这个方法就可以了。

IProgrammer接口中被通知的方法接收参数boss对象:

具体的programmer实现类,也只需要该接收通知的方法:

客户端不需要更改,输出结果和上面一样,没有问题。当有多个具体的观察者(程序员)类时,不同的类需要不同的信息,在目标类中定义多种状态,当具体的观察者接到通知后通过目标对象实例按需去状态就可以了。

推拉模型的比较:

推模型是假定目标对象知道观察者需要什么数据,相当于精准推送。拉模型目标对象不知道观察者需要什么数据,把自身对象给观察者,让观察者自己去取。

推模型使观察者模型难以复用,拉模型可以复用。

相关扩展

其实在java中已经有了观察者模式的实现,不需要自己从头写。在java.util包里面有一个类Observable,它实现了大部分我们需要的目标功能。接口Observer中定义了update方法。相比于上面自己的实现区别如下:

不需要定义观察者和目标的接口,JDK已经定义好了

在目标实现类中不需要维护观察者的注册信息,这个Observable类中帮忙实现好了。

触发方式需要先调用setChanged方法

具体的观察者实现中,update方法同时支持推模型和拉模型。

使用java中的Observable实现上面的程序:

目标对象 :

观察者对象:

客户端:

输出结果:

这样看观察者模式是不是非常简单了。

观察者模式的优点:

实现观察者和目标之间的抽象耦合:只是在抽象层面耦合了。

观察者模式实现了动态联动:一个操作会引起其他相关操作。

观察者支持广播通信:需要注意避免死循环。

观察者模式的缺点:

广播通信会引起不必要的操作。

何时使用观察者模式:

当一个抽象模型有两个方面,一个方面的操作依赖于另一个方面的状态变化时。

更改一个对象时,需要同时联动更改其他对象,但却不知道应该有多少对象需要被改变时。

当一个对象必须通知其他对象,但是又希望他们之间是松散耦合的。

模式变形

不久老板又陷入了苦恼,因为上面这种方式每次都会同时通知小华和小强,但是老板想只通知小强而不通知小华,想要区别对待观察者,例如当有用户反应公司的软件产品出现bug时,需要小华和小强去找bug并修复。但当出现不是特别紧急的bug时只需要小华一个人修改就可以了,如果出现了紧急的bug,需要马上修复的,就需要小华和小强同时去修复。该如何使用观察者实现上面的场景呢?

因为经过上面的学习,对于观察者已经了解了,代码不做太多的解释。

在这里可以把Bug当做目标,小强和小华都是观察者,首先定义观察者接口:

目标对象抽象接口:

具体的观察者实现

具体的目标实现:

客户端测试一下:

输出结果如下:

可以看出,当Bug的级别不同的时候通知的人是不一样的,一级时只通知小华,二级的时候会通知小华和小强。

好了,这就是观察者模式的全部内容了。

声明:本文为作者投稿,版权归作者个人所有。

热 文推 荐

你点的每个“在看”,我都认真当成了喜欢

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20200103A0JQ7I00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券