Fork me on GitHub

《Head First 设计模式》之观察者模式

前言

  昨天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:56
10  **/
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 Yang
13  * @create 2017-10-07 下午4:08
14  **/
15 public class WeatherData implements Subject {
16 
17     // 用来封装主题所管理的观察者
18     private List<Observer> observers;
19     private float temperature;
20     private float humidity;
21     private float pressure;
22 
23     public WeatherData() {
24         this.observers = new ArrayList<Observer>();
25     }
26 
27     /**
28      * 用于注册观察者到该主题中
29      *
30      * @param o
31      */
32     public void registerObserver(Observer o) {
33         observers.add(o);
34     }
35 
36     /**
37      * 若观察者想取消注册,则调用该方法
38      *
39      * @param o
40      */
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 temperature
70      * @param humidity
71      * @param pressure
72      */
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 Yang
11  * @create 2017-10-07 下午4:28
12  **/
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 weatherData
23      */
24     public CurrentConditionsDisplay(Subject weatherData) {
25         this.weatherData = weatherData;
26         weatherData.registerObserver(this);
27     }
28 
29     /**
30      * 当观测值更新时,将最新数据保存起来
31      *
32      * @param temperature
33      * @param humidity
34      * @param pressure
35      */
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 Yang
10  * @create 2017-10-07 下午4:39
11  **/
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内置的观察者模式

  未完待续...  

  

  

 

posted @ 2017-10-08 22:23  萍韵众生  阅读(261)  评论(0编辑  收藏  举报