今天让我们来学习一下观察者模式
一、认识观察者模式:
我们都知道报社和杂志订阅的关系,报社的业务就是出版报纸,当你向报社订阅报纸后,只要他们有新的报纸出版,就会给你送来,只要你是他们的订户,就会一直收到新的报纸。当你不想再看报纸的时候,取消订阅,他们就不会再送新报纸来。只要报社还在运营,就会一直有人(或单位)向他们订阅报纸或取消订阅报纸。
如果你了解报纸的订阅是怎么回事,其实就知道观察者模式是怎么回事,只是名称不太一样:出版者改称为“主题”(Subject),订阅者改称为“观察者”(Observer)。
下面通道一张图来更好的了解观察者模式:
二、定义
观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
(主题和观察者定义了一对多的关系。观察者依赖于此主题,只要主题状态一有变化,观察者就会被通知。根据通知的风格,观察者可能因此新值而更新。)
三、理解并实现观察者模式
1、我们先来看一下观察者模式的UML类图:
2、观察者模式提供了一种对象设计,让主题和观察者之间松耦合。从而让对象之间的互相依赖降到了最低。(这也是面向对象设计的一条非常重要的原则)
3、下面我们通过气象站的例子来实现观察者模式
(1)、此气象站的功能是当气象数据变化的时候,我们会通过方法将气象数据随时更新并显示到我们的布告板上。此系统中的三个部分是气象站(获取实际气象数据的物理装置)、WeatherData对象(追踪来自气象站的数据,并更新布告板)和布告板(显示目前天气状况给用户看)。
WeatherData对象知道如何跟物理气象站联系,以取得更新的数据。WeatherData对象会随即更新三个布告板的显示:目前状况(温度、湿度、气压)、气象统计和天气预报。
我们的工作就是建立一个应用,利用WeatherData对象取得数据,并更新三个布告板:目前状况、气象统计和天气预报。
(2)、下面我们用代码来实现我们的气象站(这里并没有将整个功能都实现,只是为了演示观察者模式)
观察者接口:
/** * @author fan_rc@suixingpay.com * @description 观察者接口 * @date 2019/8/28 21:07 */ public interface Observer { /** * @param temp * @param humidity * @param pressure * @description 当主题内容改变时,需要更新观察者的温度等状态 * @author fan_rc@suixingpay.com * @date 2019/8/28 21:16 */ void update(float temp, float humidity, float pressure); }
主题接口:
/** * @author fan_rc@suixingpay.com * @description 主题接口 * @date 2019/8/28 21:08 */ public interface Subject { /** * @param * @return * @throws * @description 注册成为观察者 * @author fan_rc@suixingpay.com * @date 2019/8/28 21:09 */ void registerObserver(Observer observer); /** * @param * @return * @throws * @description 删除观察者 * @author fan_rc@suixingpay.com * @date 2019/8/28 21:10 */ void removeObserver(Observer observer); void notifyObservers(); }
观察者1:统计显示类
/** * @author fan_rc@suixingpay.com * @description 统计显示 * @date 2019/8/28 21:33 */ public class StatisticsDisplay implements Observer { private Subject weatherData; public StatisticsDisplay(Subject weatherData) { this.weatherData = weatherData; weatherData.registerObserver(this); } @Override public void update(float temp, float humidity, float pressure) { System.out.println("统计显示收到天气数据变化消息" + temp + "-" + humidity + "-" + pressure); } }
观察者2:实时显示类
/** * @description 实时显示(观察者) * * @author fan_rc@suixingpay.com * @date 2019/8/28 21:25 */ public class CurrentConditionsDisplay implements Observer { private Subject weatherData; public CurrentConditionsDisplay(Subject weatherData) { this.weatherData = weatherData; weatherData.registerObserver(this); } @Override public void update(float temp, float humidity, float pressure) { System.out.println("实时显示观察者收到天气数据变化消息" + temp + "-" + humidity + "-" + pressure); } }
主题实现类:
/** * @author fan_rc@suixingpay.com * @description 天气数据 主题实现类 * @date 2019/8/28 21:17 */ public class WeatherData implements Subject { private ArrayList<Observer> observers; private float temperature; private float humidity; private float pressure; public WeatherData() { observers = new ArrayList<>(); } @Override public void registerObserver(Observer observer) { observers.add(observer); } @Override public void removeObserver(Observer observer) { int i = observers.indexOf(observer); if (i >= 0) { observers.remove(observer); } } @Override public void notifyObservers() { for (int i = 0; i < observers.size(); i++) { Observer observer = observers.get(i); observer.update(temperature, humidity, pressure); } } /** * @param * @return * @throws * @description 当从气象站得到更新观测值时,我们通知观察者 * @author fan_rc@suixingpay.com * @date 2019/8/28 21:23 */ public void measurementsChanged() { notifyObservers(); } /** * 设置天气数据(同时,改变天气数据,通知观察者) * * @param temperature * @param humidity * @param pressure */ public void setMeasurements(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; measurementsChanged(); } }
我们的气象站类:
/** * @description 气象站 * * @author fan_rc@suixingpay.com * @date 2019/8/28 21:27 */ public class WeatherStation { public static void main(String[] args) { WeatherData weatherData = new WeatherData(); CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData); StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData); weatherData.setMeasurements(10, 11, 12); } }
我们来看一下运行结果:
以上代码变简单的实现了我们的观察者模式。
在java中呢,其实已经内置了观察者模式。java.util包(package)内包含最基本的Observer接口与Observable类,这和我们的Subject接口与Observer接口很相似。Observer接口与Observable类使用上更方便,因为许多功能都已经事先准备好了。
下面,我们用java内置的观察者来实现一下
观察者:
/** * @author fan_rc@suixingpay.com * @description 基于java的观察者类实现观察者 * @date 2019/8/29 21:06 */ public class CurrentConditionsDisplayJava implements Observer { private Observable observable; public CurrentConditionsDisplayJava(Observable observable) { this.observable = observable; observable.addObserver(this); } @Override public void update(Observable o, Object arg) { if (o instanceof WeatherDataJava) { WeatherDataJava weatherDataJava = (WeatherDataJava) o; System.out.println(arg); } } }
主题类:
/** * @author fan_rc@suixingpay.com * @description 基于Java内部的Observable类实现被观察者类 * @date 2019/8/29 21:04 */ public class WeatherDataJava extends Observable { private float temperature; private float humidity; private float pressure; /** * @description 当从气象站得到更新观测值时,我们通知观察者 * @author fan_rc@suixingpay.com * @date 2019/8/28 21:23 */ public void measurementsChanged() { setChanged(); notifyObservers(temperature); } /** * 设置天气数据(同时,改变天气数据,通知观察者) * * @param temperature * @param humidity * @param pressure */ public void setMeasurements(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; measurementsChanged(); } }
气象站:
/** * @description 气象站 * * @author fan_rc@suixingpay.com * @date 2019/8/28 21:27 */ public class WeatherStation { public static void main(String[] args) { WeatherDataJava weatherDataJava = new WeatherDataJava(); CurrentConditionsDisplayJava currentConditionsDisplayJava = new CurrentConditionsDisplayJava(weatherDataJava); weatherDataJava.setMeasurements(10, 11, 12); } }
运行结果:
四、优缺点
1、优点:(1)观察者和被观察者是抽象耦合的;(2)建立一套触发机制。
2、缺点:(1)如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。 (2)如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。 (3)观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
五、使用场景
1、一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
2、一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
3、一个对象必须通知其他对象,而并不知道这些对象是谁。
4、需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
六、注意事项
1、使用jdk自带的观察者模式是有缺点的:(1)Observable是一个类,而不是一个接口,导致Observable类的扩展性不高,不如自己实现的观察者模式灵活。(2)Observable将某些方法保护了起来(setChanged()和clearChanged()为protected),这意味着除非继承自Observable,否则将有关键的方法不能调用。导致无法通过组合的方式使其它类获得Observable类的功能。
2、避免循环引用。
3、如果顺序执行,某一观察者错误会导致系统卡壳,一般采用异步方式。