设计模式目录:
概要
第6部分 实例演示
第1部分 基本定义
何谓观察者模式?观察者模式定义了对象之间的一对多依赖关系,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并且自动更新。
在这里,发生改变的对象称之为观察目标,而被通知的对象称之为观察者。一个观察目标可以对应多个观察者,而且这些观察者之间没有相互联系,所以么可以根据需要增加和删除观察者,使得系统更易于扩展。
观察者模式又称为发布-订阅模式。
第2部分 基本结构
首先先看观察者模式的UML类图。
分析:
Subject:目标。他把所有对观察者对戏的引用保存在一个聚集里,每一个主题都可以有多个观察者。
Observer:观察者。为所有的具体观察者定义一个接口,在得到主题的通知时能够及时的更新自己。
ConcreteSubject:具体主题。将有关状态存入具体观察者对象。在具体主题发生改变时,给所有的观察者发出通知。
ConcreteObserver:具体观察者。实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题状态相协调。
第3部分 实现观察者模式
情景是这样的:在气象观测站中,它能够追踪目前的天气状况,包括温度、适度、气压。需要实现一个布告板,能够分别显示目前的状态,气象统计和简单的预报。当气象站中获取最新的测量数据时,三种布告板必须实时更新。
下面是这个案例的设计图:
编码实现:
主题接口 Subject.java
1 /** 2 * @Title: Subject.java 3 * @Description: TODO 4 * @author :Xingle 5 * @date 2014-7-15 下午7:50:08 6 * @version 7 */ 8 9 package com.dpobserver; 10 11 /** 12 * 13 * @ClassName: Subject 14 * 15 * @author Xingle 16 * @date 2014-7-15 下午7:50:08 17 */ 18 public interface Subject { 19 20 /** 21 * 注册观察者模式 22 * @param observer 23 * @author xingle 24 * @data 2014-7-15 下午7:52:38 25 */ 26 public void registerObserver(com.dpobserver.Observer observer); 27 /** 28 * 删除观察者 29 * @param observer 30 * @author xingle 31 * @data 2014-7-15 下午7:53:25 32 */ 33 public void removeObserver(com.dpobserver.Observer observer); 34 /** 35 * 当主题状态发生改变时,这个方法需要被调用,以通知所有观察者 36 * @author xingle 37 * @data 2014-7-15 下午7:54:25 38 */ 39 public void notifyObserver(); 40 41 }
观察者接口 Observer.java
1 package com.dpobserver; 2 3 /** 4 * 观察者接口 5 * @ClassName: Observer 6 * TODO 7 * @author Xingle 8 * @date 2014-7-15 下午7:55:24 9 */ 10 public interface Observer { 11 12 public void update(float temp,float humidity,float presure); 13 14 }
布告板显示接口 DisplayElement.java
1 /** 2 * 布告板显示接口 3 * @ClassName: DisplayElement 4 * TODO 5 * @author Xingle 6 * @date 2014-7-15 下午7:57:41 7 */ 8 public interface DisplayElement { 9 10 public void display(); 11 }
WeatherData实现主题接口 WeatherData.java
1 /** 2 * @Title: WeatherData.java 3 * @Description: TODO 4 * @author :Xingle 5 * @date 2014-7-15 下午7:58:47 6 * @version 7 */ 8 9 package com.dpobserver; 10 11 import java.util.ArrayList; 12 import java.util.List; 13 14 /** 15 * 16 * @ClassName: WeatherData TODO 17 * @author Xingle 18 * @date 2014-7-15 下午7:58:47 19 */ 20 public class WeatherData implements Subject { 21 private List<com.dpobserver.Observer> observerLs; 22 private float tempterature; 23 private float pressure; 24 private float humidity; 25 26 public void setMeasurements(float tempterature, float pressure, 27 float humidity) { 28 this.tempterature = tempterature; 29 this.pressure = pressure; 30 this.humidity = humidity; 31 measurementChanged(); 32 } 33 34 public WeatherData() { 35 observerLs = new ArrayList<com.dpobserver.Observer>(); 36 } 37 38 /** 39 * 气象站得到更新的观测数据时,通知观察者 40 * 41 * @author xingle 42 * @data 2014-7-15 下午8:04:21 43 */ 44 private void measurementChanged() { 45 notifyObserver(); 46 } 47 48 /** 49 * 50 * @Description: TODO 51 * @param observer 52 * @author xingle 53 * @data 2014-7-15 下午7:59:06 54 */ 55 @Override 56 public void registerObserver(com.dpobserver.Observer observer) { 57 observerLs.add(observer); 58 } 59 60 /** 61 * 62 * @Description: TODO 63 * @param observer 64 * @author xingle 65 * @data 2014-7-15 下午7:59:06 66 */ 67 @Override 68 public void removeObserver(com.dpobserver.Observer observer) { 69 int i = observerLs.indexOf(observer); 70 if (i >= 0) { 71 observerLs.remove(i); 72 } 73 } 74 75 /** 76 * 77 * @Description: TODO 78 * @author xingle 79 * @data 2014-7-15 下午7:59:06 80 */ 81 @Override 82 public void notifyObserver() { 83 for (int i = 0; i < observerLs.size(); i++) { 84 com.dpobserver.Observer observer = observerLs.get(i); 85 observer.update(tempterature, pressure, humidity); 86 } 87 88 } 89 90 }
布告板 CurrentCondituonDisplay.java
1 /** 2 * @Title: CurrentCondituonDisplay.java 3 * @Description: TODO 4 * @author :Xingle 5 * @date 2014-7-15 下午8:16:15 6 * @version 7 */ 8 9 package com.dpobserver; 10 11 /** 12 * 布告板 13 * 14 * @ClassName: CurrentCondituonDisplay 15 * @author Xingle 16 * @date 2014-7-15 下午8:16:15 17 */ 18 public class CurrentConditionDisplay implements Observer, DisplayElement { 19 private float temperature; 20 private float humidity; 21 private float presure; 22 private Subject weatherData; 23 24 public CurrentConditionDisplay(Subject weatherData) { 25 this.weatherData = weatherData; 26 weatherData.registerObserver(this); 27 } 28 29 /** 30 * 31 * @Description: 32 * @author xingle 33 * @data 2014-7-15 下午8:16:45 34 */ 35 @Override 36 public void display() { 37 System.out.println("Current conditions:" + temperature 38 + "F degrees and " + humidity + "% humidity"+" presure :"+presure); 39 } 40 41 /** 42 * 43 * @Description: TODO 44 * @param temp 45 * @param humidity 46 * @param presure 47 * @author xingle 48 * @data 2014-7-15 下午8:16:45 49 */ 50 @Override 51 public void update(float temp, float humidity, float presure) { 52 this.temperature = temp; 53 this.humidity = humidity; 54 this.presure = presure; 55 display(); 56 } 57 58 }
测试程序 WeatherStation
1 /** 2 * @Title: WeatherStation.java 3 * @Description: TODO 4 * @author :Xingle 5 * @date 2014-7-15 下午8:25:02 6 * @version 7 */ 8 9 package com.dpobserver; 10 11 /** 12 * 13 * @ClassName: WeatherStation 14 * TODO 15 * @author Xingle 16 * @date 2014-7-15 下午8:25:02 17 */ 18 public class WeatherStation { 19 20 public static void main(String[] args){ 21 WeatherData weatherData = new WeatherData(); 22 CurrentConditionDisplay conditionDisplay = new CurrentConditionDisplay(weatherData); 23 weatherData.setMeasurements(80, 65, 30.4f); 24 weatherData.setMeasurements(82, 70, 29.2f); 25 weatherData.setMeasurements(78, 78, 40.4f); 26 } 27 28 }
执行结果:
Current conditions:80.0F degrees and 65.0% humidity presure :30.4
Current conditions:82.0F degrees and 70.0% humidity presure :29.2
Current conditions:78.0F degrees and 78.0% humidity presure :40.4
第4部分 观察者模式的优缺点
优点:
1、当两个对象之间送耦合,他们依然可以交互,但是不太清楚彼此的细节。观察者模式提供了一种对象设计,让主题和观察者之间送耦合。主题所知道只是一个具体的观察者列表,每一个具体观察者都符合一个抽象观察者的接口。主题并不认识任何一个具体的观察者,它只知道他们都有一个共同的接口。
2、观察者模式支持“广播通信”。主题会向所有的观察者发出通知。
3、观察者模式符合“开闭原则”的要求。
缺点:
1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
2、 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
3、 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
第5部分 观察者模式的适用场所
1、一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
2、一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
3、一个对象必须通知其他对象,而并不知道这些对象是谁。需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
总结
1、观察者模式定义了对象之间的一对多关系。多个观察者监听同一个被观察者,当该被观察者的状态发生改变时,会通知所有的观察者。
2、观察者模式中包含四个角色。主题,它指被观察的对象。具体主题是主题子类,通常它包含有经常发生改变的数据,当它的状态发生改变时,向它的各个观察者发出通知;观察者,将对观察主题的改变做出反应;具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致。
3、主题用一个共同的接口来更新观察者。
4、观察者与被观察者之间用松耦合方式结合。
5、有多个观察者时,不可以依赖特定的通知次序。
6、使用观察者模式,可以从被观察者处推或者拉数据。
第6部分 实例演示
客户端可以向服务器订阅RSS消息,一旦服务器更新了内容,客户端就可以接受更新的内容了。
假设现在有如下参与者:
- 抽象被观察者:IRSSServerWatched 接口,定义了添加观察者以及移除观察者的方法还有服务器发布新内容的行为方法。
- 抽象观察者:IClientWatcher 接口,定义了客户端更新内容的方法。
- 具体被观察者: ConcretRSSServerWatched.java,这个类实现了IRSSServerWatched接口
- 具体观察者:ConcretClientWatcher.java,这个类实现了IClientWatcher接口
下面来看一下具体的代码实现
1.抽象被观察者:IRSSServerWatched 接口
/** * 抽象被观察者:RSS订阅服务器接口 * @ClassName: IRSSServerWatched * @Description: TODO * @author xingle * @date 2015年10月2日 下午1:32:20 */ public interface IRSSServerWatched { public void addWatcher(IClientWatcher watcher); public void removeWatcher(IClientWatcher watcher); public void publishContent(String content); }
2.抽象观察者:IClientWatcher 接口
/** * 抽象观察者:IClientWatcher 接口 * @ClassName: IClientWatcher * @Description: TODO * @author xingle * @date 2015年10月2日 下午1:33:03 */ public interface IClientWatcher { public void update(String content); }
3.具体被观察者: ConcretRSSServerWatched
/** * 具体被观察者角色,也就是RSS服务器 * @ClassName: ConcretRSSServerWatched * @Description: TODO * @author xingle * @date 2015年10月2日 下午1:34:32 */ public class ConcretRSSServerWatched implements IRSSServerWatched { private List<IClientWatcher> mWatchersList = new ArrayList<IClientWatcher>(); /** * * @Description: TODO * @param watcher * @author xingle * @date 2015年10月2日 下午1:35:06 */ @Override public void addWatcher(IClientWatcher watcher) { // 这个方法向服务器添加注册的客户端 mWatchersList.add(watcher); } /** * * @Description: TODO * @param watcher * @author xingle * @date 2015年10月2日 下午1:35:06 */ @Override public void removeWatcher(IClientWatcher watcher) { // 这个方法将客户端从服务器移除 mWatchersList.remove(watcher); } /** * * @Description: TODO * @param content * @author xingle * @date 2015年10月2日 下午1:35:06 */ @Override public void publishContent(String content) { // 这个方法是服务器发布新内容,并通知客户端更新内容 System.out.println("服务器更新内容啦~"); for (IClientWatcher watcher : mWatchersList) { watcher.update(content); } } }
4.具体观察者:ConcretClientWatcher
/** * 具体观察者角色:RSS订阅客户端 * @ClassName: ConcretClientWatcher * @Description: TODO * @author xingle * @date 2015年10月2日 下午1:37:11 */ public class ConcretClientWatcher implements IClientWatcher{ /** * * @Description: TODO * @param content * @author xingle * @date 2015年10月2日 下午1:37:43 */ @Override public void update(String content) { System.out.println("客户端更新内容 : "+content); } }
5.下面来测试一下
/** * @ClassName: RSSTest * @Description: TODO * @author xingle * @date 2015年10月2日 下午1:38:26 */ public class RSSTest { public static void main(String[] args){ IRSSServerWatched server = new ConcretRSSServerWatched(); IClientWatcher one = new ConcretClientWatcher(); IClientWatcher two = new ConcretClientWatcher(); IClientWatcher three = new ConcretClientWatcher(); server.addWatcher(one); server.addWatcher(two); server.addWatcher(three); server.publishContent("服务器发布新内容了 "); } }
执行结果:
该读书笔记的代码和部分语句均来自《Head First 设计模式》