声明: 本文
内容属于《Head First 设计模式》阅读笔记
,文中涉及到的知识案例等直接或间接来源于该书。《Head First 设计模式》
通过有趣的图表+文字的形式,让人自然学习设计模式,非常棒
,推荐阅读
。
观察者模式概念:
定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
图示说明:
注:观察者模式是所有设计模式中应用最多、最广的设计模式,不论是MVC等框架,还是监听器、MQ等等,都有用到观察者模式。
案例(辅助理解):
提示: 观察者模式使用灵活,下面只是一个简单的例子,在不同的情境下,可能有不同的表现形式。
情景介绍:
现在有一个气象站,其能实时提供变化了的气象数据;同时有三个公告板,分别要根据气象站的气象数据信息,显示:实时的气象信息、统计后的气象信息(如:统计温度平均值等等)、气象预报信息。
从观察者模式出发:
气象站可视为被观察者Subject,三个公告板可视为三个观察者Observer。再进行基本的抽象-封装-继承/实现-多态,可以有以下UML类图:
其中Subject为被观察者需要实现的接口,其中三个主要的方法分别是:注册(观察者开始观察被观察者)、移除(观察者取消观察被观察者)、通知(被观察者发送消息给观察者);Observer为观察者需要实现的接口,只有一个方法:更新(被观察者发送消息后,会走此方法进行各个观察者的逻辑操作)。
注:这只是最基本的方法示例,可以根据实际业务情况灵活变通或对其进行拓展。对观察者模式理念切实的理解,还需要借助一下具体实现类,下面会给出代码及测试示例,以供进一步理解此模式。
上述观察者模式示例中的几个核心类:
-
Subject:
import com.szlaozicl.designpattern.observer.observer.Observer; /** * 主题(即:可/被观察者) * * 注:所有被观察者均应实现此接口。 * * @author JustryDeng * @date 2019/10/12 11:53 */ public interface Subject { /** * 注册(新增)观察者 * * @param o * 要注册的观察者对象 * * @return 是否注册成功 * @date 2019/10/12 11:53 */ boolean registerObserver(Observer o); /** * 注销(移除)观察者 * * @param o * 要注销的观察者对象 * * @return 是否注销成功 * @date 2019/10/12 11:53 */ boolean removeObserver(Observer o); /** * 通知观察者们 * * @date 2019/10/12 11:53 */ void notifyObservers(); }
-
WeatherData:
import com.szlaozicl.designpattern.observer.observer.Observer; import com.szlaozicl.designpattern.observer.subject.Subject; import lombok.Data; import java.util.ArrayList; /** * 气象数据 * * @author JustryDeng * @date 2019/10/12 12:09 */ @Data public class WeatherData implements Subject { /** 温度 */ private float temperature; /** 湿度 */ private float humidity; /** 气压(千帕) */ private float pressure; /** * 观察者容器 * * 注: 观察者的 注册 与 注销, 操作此集合即可 */ private ArrayList<Observer> observers = new ArrayList<>(8); @Override public boolean registerObserver(Observer o) { return observers.add(o); } @Override public boolean removeObserver(Observer o) { int index = observers.indexOf(o); if (index >= 0) { observers.remove(index); } return true; } @Override public void notifyObservers() { for (Observer observer : observers) { // 这里采用: 观察者主动从 被观察者拉数据的方式 // void update(Subject subject, Object... args); observer.update(this); } } /** * 当参数值改变时,会调用此方法 * * @date 2019/10/12 12:33 */ private void measurementsChanged() { notifyObservers(); } /** * 设置参数值 * * @param temperature * 温度 * @param humidity * 湿度 * @param pressure * 气压 * * @date 2019/10/12 12:33 */ public void setMeasurements(float temperature, float humidity, float pressure) { boolean haveChange = this.temperature != temperature || this.humidity != humidity || this.pressure != pressure; if (haveChange) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; measurementsChanged(); } } }
-
Observer:
import com.szlaozicl.designpattern.observer.subject.Subject; /** * 观察者 * * 注:所有观察者均应实现此接口。 * * @author JustryDeng * @date 2019/10/12 11:57 */ public interface Observer { /** * 更新 * * 注: 当被观察者发生变化时, 有两种方式将数据 从主题传递给观察者 * 1. 被观察者 主动传参给 观察者, 即: 被观察者主动推数据给观察者 * 2. 观察者 通过调用 被观察者的一些方法(如:getter)主动获取相关数据, 即:观察者主动从被观察者拉数据 * * @param subject * 主题(即:被观察者对象) * 注:用于 观察者主动从被观察者拉数据。 * @param args * 被观察者注定push给观察者的参数 * 注:用于 被观察者主动推数据给观察者。 * * @date 2019/10/12 12:00 */ void update(Subject subject, Object... args); }
-
ConcreteObserver:
import com.szlaozicl.designpattern.observer.observer.Observer; import com.szlaozicl.designpattern.observer.other.Display; import com.szlaozicl.designpattern.observer.subject.Subject; import com.szlaozicl.designpattern.observer.subject.impl.WeatherData; import lombok.Data; /** * 实时信息 观察者 * * @author JustryDeng * @date 2019/10/12 12:48 */ @Data public class ConcreteObserver implements Observer, Display { /** 温度 */ private float temperature; /** 湿度 */ private float humidity; /** 气压(千帕) */ private float pressure; @Override public void update(Subject subject, Object... args) { if (subject instanceof WeatherData) { WeatherData weatherData = (WeatherData)subject; this.temperature = weatherData.getTemperature(); this.humidity = weatherData.getHumidity(); this.pressure = weatherData.getPressure(); } display(); } @Override public void display() { String info = "\n【实时信息观察者ConcreteObserver】观察到的数据是:\n" .concat("\t当前温度: ").concat(String.valueOf(temperature)).concat("\n") .concat("\t当前湿度: ").concat(String.valueOf(humidity)).concat("\n") .concat("\t当前气压: ").concat(String.valueOf(pressure)).concat("\n"); System.out.println(info); } }
注:观察者ForecastObserver与观察者StatisticsObserver的代码,这里就不再给出了,可详见文末完整代码托管链接。
测试一下:
- 测试类:
import com.szlaozicl.designpattern.observer.observer.impl.ConcreteObserver; import com.szlaozicl.designpattern.observer.observer.impl.ForecastObserver; import com.szlaozicl.designpattern.observer.observer.impl.StatisticsObserver; import com.szlaozicl.designpattern.observer.subject.impl.WeatherData; import java.util.concurrent.TimeUnit; /** * 测试 --- 观察者模式 * * @author JustryDeng * @date 2019/10/12 13:23 */ public class Test { /** * 函数入口 */ public static void main(String[] args) throws InterruptedException { /// 创建 被观察者 WeatherData weatherData = new WeatherData(); /// 创建 观察者们 ConcreteObserver concreteObserver = new ConcreteObserver(); ForecastObserver forecastObserver = new ForecastObserver(); StatisticsObserver statisticsObserver = new StatisticsObserver(); /// 观察者 进行注册,开始观察 被观察者 boolean resultOne = weatherData.registerObserver(concreteObserver); boolean resultTwo = weatherData.registerObserver(forecastObserver); boolean resultThree = weatherData.registerObserver(statisticsObserver); System.out.println("concreteObserver注册观察weatherData, " + (resultOne ? "成功!" : "失败!")); System.out.println("forecastObserver注册观察weatherData, " + (resultTwo ? "成功!" : "失败!")); System.out.println("registerObserver注册观察weatherData, " + (resultThree ? "成功!" : "失败!")); /// ------------------------ 测试验证 // 被观察者 第一次数据发生变化 TimeUnit.MILLISECONDS.sleep(100); System.err.println("\n被观察者 第一次 数据发生变化!"); weatherData.setMeasurements(10.5F, 0.23F, 50.2F); // 被观察者 第二次数据发生变化 TimeUnit.MILLISECONDS.sleep(100); System.err.println("被观察者 第二次 数据发生变化!"); weatherData.setMeasurements(27.5F, 0.68F, 100.2F); /// 观察者 取消观察 boolean resultFour = weatherData.removeObserver(concreteObserver); boolean resultFive = weatherData.removeObserver(forecastObserver); boolean resultSix = weatherData.removeObserver(statisticsObserver); System.out.println("concreteObserver取消观察weatherData, " + (resultFour ? "成功!" : "失败!")); System.out.println("forecastObserver取消观察weatherData, " + (resultFive ? "成功!" : "失败!")); System.out.println("registerObserver取消观察weatherData, " + (resultSix ? "成功!" : "失败!")); // 被观察者 第三次数据发生变化 TimeUnit.MILLISECONDS.sleep(100); System.err.println("\n被观察者 第三次 数据发生变化!"); weatherData.setMeasurements(30.5F, 0.88F, 680.2F); } }
- 运行测试类main方法,控制台输出:
观察者模式学习完毕 !
^_^ 如有不当之处,欢迎指正
^_^ 参考资料
《Head First 设计模式》Eric Freeman & Elisabeth Freeman with Kathy Sierra & Bert Bates著,O’Reilly Taiwan公司译,UMLChina改编
^_^ 测试代码托管链接
https://github.com/JustryDeng…DesignPattern
^_^ 本文已经被收录进《程序员成长笔记》 ,笔者JustryDeng