有一个设计模式帮助你的对象知悉现状,不会错过该对象感兴趣的事情,甚至在对象运行时可决定是否要继续被通知,观察者模式是JDK中使用最多的设计模式之一,非常有用。无论是在JDK还是Android开发当中,我们很容易发现观察者模式的运用之处,如我们经常遇到的点击事件,通过Button控件的诸如Listener 的方法,onClickListener就是观察/订阅到了按钮的点击事件,从而就可以执行对相应的逻辑,不同的动作会有不同的观察者,如单击、长按、连续两次点击等都有对应的Listener。
观察者模式概述
主题 + 订阅者 = 观察者设计模式
现在假设有一个气象站,气象站会根据天气的变化设置新的气象数据 (温度、湿度、气压) ,这些数据会展示在气象看板上面,一旦气象站发布了新的数据,则看板也必须立马更新展示的数据。
在这个例子中,主题就是天气数据,订阅者就是显示装置。一旦有新的天气数据,显示装置立马展示新的数据。
观察者模式代码实现
首先定义一个主题的接口,所有的主题都需要实现这个接口:
1 | /** |
天气数据就是一个主题,因此定义出天气主题的类:
1 | import java.util.ArrayList; |
上面的观察者还未定义呢,还是先定义一个统一的观察者数据更新方法的接口
1 | /** |
接下来定义一个展示数据的接口,作为显示装置都需要实现的接口:
1 | public interface DisplayElement { |
然后就是显示装置的具体实现,目前只实现一种那就是展示最新的气象数据:
1 | /** |
接下来测试一下写的观察者模式:
1 | public class Test { |
JDK内置的观察者模式
观察者模式是对象的行为模式,在对象之间定义了一对多的依赖关系,就是多个观察者和一个被观察者之间的关系,当被观察者发生变化的时候,会通知所有的观察者对象,他们做出相对应的操作。 在观察者模式,我们又分为推模型和拉模型两种方式,上面演示的内容是推模型。
在JDK内已经有实现好的观察者模式API,java.util包内包含最基本的Observer接口和Observable类,这与我们的Subject接口和Observer接口很相似。实际上Observer接口与Observable类使用起来更方便,因为很多功能已经提前准备好了。下面演示一个通过JDK的API实现拉模型的例子。
1、如何把对象变成观察者
实现观察者接口java.util.Observer,然后调用任何Observable对象的addObserver()方法,不想当观察者的时候,调用deleteObserver()方法即可。
2、被观察者如何送出通知
首先扩展java.util.Observer接口产生被观察者类,然后调用两个方法:
- 先调用setChanged()方法,标记状态已经改变的事实
- 然后调用notifyObservers()方法中的一个,notifyObservers()或者notifyObservers(Object arg)
3、观察者如何接收通知
同以前的update()方法一样,只是方法参数略有不同:
1 | update(Observable o, Object arg) |
第一个参数Observable就是主题对象,好让观察者知道是哪个主题通知它的;第二个参数就是上面的例子中的参数,即数据对象。
如果使用推模式,则可以把数据当做数据对象传入notifyObservers(Object arg)中。否则观察者就必须从被观察者对象中拉取数据,我们把上面气象站的例子重做一次。
4、关于setChanged()
setChanged()方法用于标记状态已经改变的事实,好让notifyObservers()知道当它被调用时就应该更新观察者。如果调用notifyObservers()之前没有调用setChanged(),则观察者不会被通知,伪代码如下:
1 | setChaged(){ |
这样做的目的就是在更新观察者的时候能有更多的弹性,比如在你想在气象温度变化0.5度以上才通知观察者,就需要通过调用setChanged这样的方式进行数据的有效更新。
JDK内置观察者重做气象站
WeatherData.java
1 | import java.util.Observable; |
CurrentConditionDisplay.java (DisplayElement和前面的例子一样)
1 | import java.util.Observable; |
主题对象在通知观察者的时候,只传递少量信息。如果观察者需要更具体的信息,由观察者主动到主题对象中获取,相当于是观察者从主题对象中拉数据。一般这种模型的实现中,会把主题对象自身通过update()方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过这个引用来获取了。
观察者模式的优缺点
1、优点
首先是松耦合,当两个对象之间松耦合,它们依旧可以交互,但是不太清楚彼此的细节,观察者模式就提供了这样一种对象设计,让主题和观察者之间松耦合。
主题值需要知道观察者实现了某个接口,也就是Observer接口,不需要知道具体观察者实现类是什么,也不用关系观察者的实现细节。任何时间我们都可以动态的添加或者移除观察者、也包括替换新的观察者等操作,主题都不会受到影响。改变被观察者和观察者任意一方都不会影响另一方,这就是松耦合特点。
2、缺点
接下来说说缺点, 如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间,也就是说同一个主题的观察者不能太多,太多了每次通知都是需要消耗时间的。
而且如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃。在使用观察者模式是要特别注意这一点。
接下来讨论一个问题,但是却不是观察者模式的问题,而是JDK内置的观察者模式的问题。java.util.Observable是一个类而不是一个接口,如果要使用必须继承这个类,这其实限制了Observable的复用能力,而且通过源码可以看到setChanged()是受保护的权限,这意味着只能继承java.util.Observable,这违反了“多用组合、少用继承”的原则。平时使用的时候应该多注意这个问题,有必要的话最好自己实现一套观察者模式。
参考资料
- 本文作者: Tim
- 本文链接: https://zouchanglin.cn/2020/09/19/2586075670.html
- 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 许可协议。转载请注明出处!