前言
昨天lz写了一篇关于策略模式的文章,感觉这篇文章写了很久才完成,而且自我感觉写的并不是很好。究其原因lz发现有以下几点。第一,lz对设计模式刚开始接触,现在也仅仅只是停留在概念阶段,没有付诸实践,没有将这些设计理论运用到项目中去,可能现在对lz来讲很难讲其吃透,不过这也能理解,对新知识的学习毕竟是要循序渐进。第二,lz觉得自己的写作能力有所欠缺,高中语文确实是没学好呃,这个只能慢慢改善了。
关于写博客这件事,lz看到一句话觉得说的很合理,故献给大家望共勉。“当别人请我给他们一些写 blog 的建议,我总是回他:挑个你自认为可以的时间行程安排,什么时候开始写 blog,预计多久写一篇文,开始动工,并坚持下去。在你这么做之前,任何建议对你来说都是不重要的。你文章是否写得很糟糕不重要,是否没有任何人会看你的 blog 不重要,是不是没啥有趣的东西可以记录也不重要。重要的是,只要你能透过写文来表现出写作的意愿,而且渴望持续地写作,检视、思考与改善自己的写作,你终究会成功的。”
今日分享
正如之前写到的一样,这块内容以后每篇文章前面都会推送给大家。今天给大家带来了两句话。
1.该舍的,舍不得,只顾着跟往事瞎扯,等你发现时间是贼了,它早已偷光你的选择!
2.一句英文,Life is like a ball, your initial steps of the church who may not be able to accompany you come to finish. 人生就像一场舞会,教会你最初舞步的人,未必能陪你走到散场。不乱于心,不困于情,不畏将来,不念过往。“如果微笑成为习惯,快乐也会成为习惯”
观察者模式
观察者模式是JDK中使用最多的模式之一,比如我们常用的java.util包,JavaBeans和Swing中,后续我们再做进一步介绍。可通俗点理解该模式为:能让你的对象知悉现况,不会错过该对象感兴趣的事情。
认识观察者模式
首先看看报纸和杂志的订阅是怎么回事:
1.报社的业务就是出版报纸。
2.向某家报社订阅报纸,只要他们有新报纸出版,就会给你送来。你要你是他们的订户,你就会一直受到新报纸。
3.当你不想再看报纸的时候,取消订阅,他们就不会再送新报纸来。
4.只要报社还在运营,就会一直有人(或单位)向他们订阅报纸或取消报纸。
我想大家从上面的这份关系中可以初步了解观察者模式,其实出版者+订阅者=观察者模式。不过我们习惯将出版者称为“主题”(Subject),订阅者称为“观察者”(Observer)。现用浅显的话语解释下,首先主题对象管理着某些数据,观察者已经订阅(注册)主题以便在主题数据改变时能够收到更新。其次当主题内的数据改变时,就会通知观察者,也即:新的数据会以某种形式送到观察者手上。当然,如果某个对象不是观察者,即使主题数据更新时也不会将新的数据通知到此对象。
定义观察者模式
现给出观察者模式官方解释:定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。可以理解为,主题和观察者定义了一对多关系。观察者依赖于此主题,只要主题状态一有变化,观察者就会被通知。根据通知的风格,观察者可能因此新值而更新。其实实现观察者模式的方法不止一种,但是以包含Subject和Observer接口的类设计的做法最为常见,下面给出该模式类图。
UML类图
气象站实例
现给出具体需求:某公司需要建立下一代气象站,且必须建立在给出的WeatherData对象上,由该对象负责追踪目前的天气状况(温度,湿度,气压)。希望能建立一个应用,有三种布告板,分别显示目前的状况,气象统计及简单的预报。当WeatherData对象获得最新的测量数据时,三种布告板必须要实时更新。最后这是一个可扩展的气象站,也即:希望能实现自己的布告板并插入到此应用中。
现给出该应用UML图:
下面我们再来具体实现该气象站,首先从接口开始,
主题接口:
1 package xin.yangmj.observer.subject; 2 3 import xin.yangmj.observer.observe.Observer; 4 5 /** 6 * 这是主题接口,所有具体主题都应实现此接口 7 * 8 * @author Eric Yang 9 * @create 2017-10-07 下午3:5610 **/11 public interface Subject {12 13 public void registerObserver(Observer o);14 public void removeObserver(Observer o);15 public void notifyObserver();16 }
观察者接口:
1 package xin.yangmj.observer.observe; 2 3 /** 4 * 这是观察者接口,所有具体观察者都应实现此接口 5 * 6 * @author Eric Yang 7 * @create 2017-10-07 下午4:00 8 **/ 9 public interface Observer {10 11 public void update(float temp, float humidity, float pressure);12 }
布告板接口,也即:具体布告板要实现此接口:
1 package xin.yangmj.observer.observe; 2 3 /** 4 * 这是布告板接口,用来展示获取到的数据 5 * 6 * @author Eric Yang 7 * @create 2017-10-07 下午4:06 8 **/ 9 public interface DisplayElement {10 11 public void display();12 }
然后,在写出具体的实现类
首先是具体主题实现类:
1 package xin.yangmj.observer.subject.impl; 2 3 import xin.yangmj.observer.observe.Observer; 4 import xin.yangmj.observer.subject.Subject; 5 6 import java.util.ArrayList; 7 import java.util.List; 8 9 /**10 * 这是WeatherData类,实现主题接口11 *12 * @author Eric Yang13 * @create 2017-10-07 下午4:0814 **/15 public class WeatherData implements Subject {16 17 // 用来封装主题所管理的观察者18 private Listobservers;19 private float temperature;20 private float humidity;21 private float pressure;22 23 public WeatherData() {24 this.observers = new ArrayList ();25 }26 27 /**28 * 用于注册观察者到该主题中29 *30 * @param o31 */32 public void registerObserver(Observer o) {33 observers.add(o);34 }35 36 /**37 * 若观察者想取消注册,则调用该方法38 *39 * @param o40 */41 public void removeObserver(Observer o) {42 43 int i = observers.indexOf(0);44 if (i >= 0) {45 observers.remove(i);46 }47 }48 49 /**50 * 通知观察者,将主题最新数据告知每个注册的观察者51 */52 public void notifyObserver() {53 54 for (Observer observer : observers) {55 observer.update(temperature, humidity, pressure);56 }57 }58 59 /**60 * 当从气象站得到更新观测值时,通知观察者61 */62 public void measurementsChanged() {63 notifyObserver();64 }65 66 /**67 * 动态改变观测值68 *69 * @param temperature70 * @param humidity71 * @param pressure72 */73 public void setMeasurements(float temperature, float humidity, float pressure) {74 this.temperature = temperature;75 this.humidity = humidity;76 this.pressure = pressure;77 }78 79 // WeatherData的其他方法80 }
给出某一个布告板实现类:
1 package xin.yangmj.observer.observe.impl; 2 3 import xin.yangmj.observer.observe.DisplayElement; 4 import xin.yangmj.observer.observe.Observer; 5 import xin.yangmj.observer.subject.Subject; 6 7 /** 8 * 具体布告板,相当于具体观察者实现类 9 *10 * @author Eric Yang11 * @create 2017-10-07 下午4:2812 **/13 public class CurrentConditionsDisplay implements Observer, DisplayElement{14 15 private float temperature;16 private float humidity;17 private Subject weatherData;18 19 /**20 * 通过构造器,可以将此观察者注册到主题中21 *22 * @param weatherData23 */24 public CurrentConditionsDisplay(Subject weatherData) {25 this.weatherData = weatherData;26 weatherData.registerObserver(this);27 }28 29 /**30 * 当观测值更新时,将最新数据保存起来31 *32 * @param temperature33 * @param humidity34 * @param pressure35 */36 public void update(float temperature, float humidity, float pressure) {37 this.temperature = temperature;38 this.humidity = humidity;39 display();40 }41 42 /**43 * 在该布告板上展示最新的观测值数据44 */45 public void display() {46 System.out.println("Current conditions: " + temperature +47 "F degrees and " + humidity + "% humidity");48 }49 }
启动气象站,测试用例:
1 package xin.yangmj.observer; 2 3 import xin.yangmj.observer.observe.impl.CurrentConditionsDisplay; 4 import xin.yangmj.observer.subject.impl.WeatherData; 5 6 /** 7 * 测试类,可用于启动气象站 8 * 9 * @author Eric Yang10 * @create 2017-10-07 下午4:3911 **/12 public class WeatherStation {13 14 public static void main(String[] args){15 16 // 创建具体主题,并初始化所管理的观察者为空17 WeatherData weatherData = new WeatherData();18 19 // 暂时只写一个面板,其他类似eg:StatisticsDisplay, ForecastDisplay等20 // 也可以后续扩展,实现自己特定的面板21 // 创建该观察者,隐含了将该观察者注册到上面具体主题中22 CurrentConditionsDisplay currentConditionsDisplay =23 new CurrentConditionsDisplay(weatherData);24 25 weatherData.setMeasurements(80, 65, 30.4f);26 // 通知观察者27 weatherData.notifyObserver();28 // 测量值发生变化29 weatherData.setMeasurements(82, 70, 29.3f);30 weatherData.measurementsChanged();31 }32 }
运行结果:
OK,以上就是整个气象站应用的代码。现对整个应用做个总结如下
设计原则
观察者模式遵循了以下几个设计原则:
1.为了交互对象之间的松耦合而努力。也即:让主题和观察者之间松耦合。关于观察者的一切,主题只知道观察者实现了某个接口(也就是Observer接口),主题不需要知道观察者的具体类是谁,做了些什么或其他任何细节。有新类型的观察者出现时,主题的代码不需要修改,因为主题唯一依赖的东西是一个实现Observer接口的对象列表。我们可以独立地复用主题或观察者,改变主题或观察者其中一方,并不会影响另一方,所以二者是松耦合的。
2.封装变化。在此模式中,会改变的是主题的状态,以及观察者的数目是类型。用这个模式,你可以改变依赖于主题状态的对象,却不必改变主题,也即:提前规划。
3.针对接口编程。主题和观察者都是用接口:观察者利用主题的接口想主题注册,而主题利用观察者接口通知观察者。这样可以让二者之间运作正常,又同时具有松耦合的有点。
4.多用组合,少用继承。该模式利用“组合”将许多观察者组合进主题中。对象之间的这种关系不是通过继承产生的,而是在运行时利用组合的方式来产生的。
使用Java内置的观察者模式
未完待续...