深入浅出学设计模式(六)之观察者模式
今天我们一起学习一下观察者模式,该模式在日常开发中能用的场景非常多,比如:电商平台的到货通知,csdn 博客的关注功能,或者手机 app 中点击按钮实现某个功能,等等。接下来我们就看看如何使用设计模式吧。
观察者模式
1 观察者模式解决的问题
今天,我们先不一上来就介绍观察者模式的定义、UML 图及如何实现等,我们先根据一个简单的例子来体会一下,如果不使用观察者模式应该如何解决问题。
需求:我们在订机票后,常常会关注该航班的动态,是否推迟、起飞时间等。假设我们即将乘坐的是东方航空,东方航空有一个专门的服务:航班动态服务,可以提供航班实时数据,该实时数据有很多平台都想使用,比如:东航自己的 web 网站需要显示这些数据、携程和智行也想要在各自的网站中为用户显示该航班最新的动态信息。 那么不使用观察者模式如何完成该功能呢。
1.1 轮询
可能我们最容易想到的办法就是轮询,各个想要获取航班实时动态的平台每隔一段时间就去调用东航的航班动态服务。类似下图:
图片画的不好,但能说明问题,在携程网的代码中开启了一个线程每隔 5 秒就去东航航班动态服务获取一下是否有最新信息,东航的 web 网站也是如此每隔 1 秒就去获取。假如后面还有去哪网、美团都想获取航班最新动态信息,如果都用这种轮询的方式的话,估计要不了几下东航航班动态服务就挂掉了。就比如:一个人很想买苹果最新款手机,他每隔一个小时就去店里问店员:新款手机有货了吗?如果有很多人都是这种想法的话,我们暂且不说这些人自己累不累,估计这些店员都要被他们问累死了。
所以这种轮询的方式的效率很低、并且很浪费系统资源,不建议使用。
1.2 简单的通知方式
通知方式的思路就是:服务端维护每个客户的信息,当有新的内容更新时,服务端就一 一通知这些客户。 当用户第一次去店里问有无新货时,店员就将该用户的电话号码记录下来,并告诉他:你不用每隔一小时就跑过来问,一有新货我就立马打电话通知你。所以店员需要保存用户的联系方式。
用简单的通知方式如何实现前面的获取航班最新动态信息呢?
我们先画一个简单的 UML 图:
在这个图中,我们主要有两个类:EastAirLineService,该类主要用于向其他客户端平台提供航班最新动态信息;EastAirWeb 类就是使用 EastAirLineService 的服务的平台,其中的 update() 方法供 EastAirLineService 使用,当有最新动态时就调用 eastAirWeb 的update() 方法通知它。为达到这种通知效果,就要在 EastAirLineService 类中聚合一个 EastAirWeb 的实例。
这种问题较轮询方式的优点在于:客户端 EastAirWeb 就不需要每隔一段时间去获取最新信息,而是由 EastAirLineService 来通知客户端,这样做从很大程度上都节约了双方的系统资源。
该方式如果是在日常生活中,已经算是很完美的方式了,但是在编写代码时,这种方式很少采用,不易于扩展。假如现在美团网和携程网都想要获取东航航班动态服务,我们画出如下 UML 图:
从上面的 UML 图就很简单的反应出问题:当有新的客户端平台想要收到东航航班动态服务的通知时,就要再在 EastAirLineService 类中增加一个属性,还要再增加调用该平台的 update() 方法的代码,所以很麻烦,不利于扩展。不支持动态扩展,还要将服务停止后才能更新代码。
该方式的代码很简单,大家可以参考一下:示例代码的 com.llk.observer.example 。
2 观察者模式定义
观察者模式也叫发布-订阅模式、监听器模式。
定义:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
该定义不难理解,我们以前面的航班动态的例子为例:东航航班动态服务就有多个依赖对象,即客户端平台,他们依赖东航航班动态服务。所以他们是一对多的关系,东航航班动态服务是“一”的一方,客户端平台是“多”的一方。当东航航班动态服务有最新的航班动态信息时就立即调用的客户端的接口(方法)来通知他们。
其实我们将前面的第二种方法进行一些修改就是观察者模式了。对于所有的客户端平台来说,其实他们之间很相似,各自都提供一个 update() 方法给东航航班动态服务调用来获取最新动态,所以我们可以将这些客户端类抽象为一个接口,让这些客户端再实现这个接口。那么在东航航班动态服务 EastAirLineService 类中,我们也就可以不用维护每一个客户端了,而是将他们维护在一个集合中,当有新动态时就遍历该集合中的每一个客户端并调用他们的 update() 方法。
3 UML 图
根据前面的分析,我们画出如下 UML 图:
在观察者模式中,有如下 4 中角色:
Subject:抽象主题类。该接口主要定义了三个方法:注册观察者、移出观察者通知观察者。
ConcreteSubject:具体的主题类,即被观察的对象。比如前面的东航航班动态服务被各个客户端观察。该类需要维护一个观察者列表,并实现 Subject 接口的方法,它具有管理这些观察者的功能,可以动态的增加或删除一个观察者,只有注册了的观察者才能获取到最新的动态信息。
Observer:抽象观察者类。定义具体观察者的行为,当观察的 Subject 有变化时,ConcreteSubject 就调用该方法。
ConcreteObserver:具体的观察者类,实现 Observer 接口。
对 Subject 和 ConcreteSubject 的深入理解:一般来将,这里的主题可能就是客户端所关心的航班动态服务、或者是读者们订阅的《意林》杂志本身,这两者是不关心谁需要接入或者订阅自己,而是有专门的管理员等来维护观察者。在观察者模式中我们将被观察的内容和对观察者的管理统一在一起了:ConcreteSubject,这两种方式都是可以的。
4 东航航班动态服务示例
接下来,我们就已前面的东航航班动态服务示例来介绍一下观察者模式如何使用。
我们先看一下 UML 图
示例代码:
AirLineService.java:Subject:主要定义管理观察者的方法。
public interface AirLineService {
public void registerObserver(Observer observer);
public void removeObserver(Observer observer);
public void notifyObservers();
}
EastAirLineService.java:ConcreteSubject。
/**
* 该类是东航的航班动态服务类
* 它的数据由机场塔台提供
* 这些数据可以在东航自己的网站上显示,也可以提供给第三方平台使用:携程、智行
*/
public class EastAirLineService implements AirLineService {
private AirData airData;
//观察者列表
private ArrayList<Observer> observerList;
public EastAirLineService(){
observerList = new ArrayList<Observer>();
}
//注册一个观察者
@Override
public void registerObserver(Observer observer) {
if(null != observer) {
observerList.add(observer);
}
}
//移除一个观察者
@Override
public void removeObserver(Observer observer) {
if(!observerList.contains(observer)) {
observerList.remove(observer);
}
}
//通知所有的观察者有最新动态信息
@Override
public void notifyObservers() {
for(Observer o:observerList){
o.update(airData);
}
}
public void setAirData(AirData airData) {
this.airData = airData;
notifyObservers();
}
}
Observer.java:抽象的观察者接口。
public interface Observer {
public void update(AirData airData);
}
EastAirWebObserver.java:ConcreteObserver:具体的观察者类,其中的 update() 方法由 EastAirLineService 调用。
//东方航空自己的 web 网站
public class EastAirWebObserver implements Observer {
@Override
public void update(AirData airData) {
System.out.println("东航web网站获取到最新航班动态:"+airData.toString());
}
}
XieChengObserver:具体的观察者类:
//携程网
public class XieChengObserver implements Observer {
@Override
public void update(AirData airData) {
System.out.println("携程获取到最新航班动态:"+airData.toString());
}
}
Test.java:测试类。
public class Test {
public static void main(String[] args) {
//创建东航航班动态服务
EastAirLineService service = new EastAirLineService();
//创建携程网客户端,并注册
Observer xieCheng = new XieChengObserver();
service.registerObserver(xieCheng);//注册
Observer eastAirWeb = new EastAirWebObserver();
service.registerObserver(eastAirWeb);//注册
//模拟一组数据
AirData airData = new AirData();
airData.setSeq("FM42412");
airData.setDelay(true);
airData.setStart("17:30");
airData.setEnd("19:35");
//有最新的航班动态信息
service.setAirData(airData);
}
}
运行结果:
5 示例代码地址
本文地址:https://blog.csdn.net/llk15884975173/article/details/108853424
上一篇: 设计模式 – 观察者模式