介绍
观察者模式。观察者顾名思义就是有监听的意思,可以用来实现对象的监听。观察者模式主要有两种写法:一种是监听到消息之后主动获取,另一种就是对方主动推送消息到观察者。
什么是观察者模式
观察者模式(Observer Pattern)也叫做发布订阅模式,其定义了对象之间的一对多依赖,让多个观察者对象同时监听一个主体对象,当主体对象发生变化时,它的所有依赖者(观察者)都会收到通知并更新,观察者模式属于行为型模式。
push 模式示例
下面我们就以获取天气预报中的气温举例进行说明,看看观察者模式应该如何实现(这里我们需要新建一个 observer 目录,相关类创建在 observer 目录下)。
- 新建一个观察者接口 Observer.java。 ```java package observer;
 
public interface Observer { void update(float temperature);//更新天气信息 }
package observer; public interface Observer { void update(float temperature);//更新天气信息 }- 接下来新建一个 Subject.java 类,用来管理观察者,主要定义了三个方法。```javapackage observer;public interface Subject {void registerObserver(Observer o);//注册观察对象void removeObserver(Observer o);//移除观察对象void notifyObservers();//通知观察对象}
package observer; public interface Subject { void registerObserver(Observer o);//注册观察对象 void removeObserver(Observer o);//移除观察对象 void notifyObservers();//通知观察对象 }
- 接下来再建立一个 WeatherData.java 类,实现 Subject 接口。
 
package observer;import java.util.ArrayList;import java.util.List;public class WeatherData implements Subject {private List<Observer> observers;//观察者不止一个,所以用list进行维护private float temperature;//温度public void setMessurements(float temperature){this.temperature = temperature;notifyObservers();//气温信息发生变化时,通知所有观察者}public WeatherData() {//初始化listthis.observers = new ArrayList<>();}/*** 注册观察者* @param o*/@Overridepublic void registerObserver(Observer o) {observers.add(o);}/*** 移除观察者* @param o*/@Overridepublic void removeObserver(Observer o) {int i = observers.indexOf(o);if(i >= 0){observers.remove(i);}}/*** 通知所有观察者*/@Overridepublic void notifyObservers() {for (Observer observer : observers){//遍历所有观察者observer.update(temperature);//通知观察者更新数据信息}}}
package observer; import java.util.ArrayList; import java.util.List; public class WeatherData implements Subject {     private List
WeatherData 类就相当于是一个被观察者,所以接下来我们还需要一个观察者。
- 新建一个观察者类 WeatherDisplay.java,需要实现 Observer 接口。
 
package observer;public class WeatherDisplay implements Observer {private Subject subject;//维护观察者private float temperature;//温度public WeatherDisplay(Subject subject) {//注册监听对象this.subject = subject;subject.registerObserver(this);}@Overridepublic void update(float temperature) {//当被观察者气温发生变化会调用这个方法,也就等于更新了观察者对象的数据this.temperature = temperature;}public void display(){System.out.println("当前最新的温度为:" + temperature);}}
package observer; public class WeatherDisplay implements Observer { private Subject subject;//维护观察者 private float temperature;//温度 public WeatherDisplay(Subject subject) {//注册监听对象 this.subject = subject; subject.registerObserver(this); } @Override public void update(float temperature) {//当被观察者气温发生变化会调用这个方法,也就等于更新了观察者对象的数据 this.temperature = temperature; } public void display(){ System.out.println(“当前最新的温度为:” + temperature); } }
- 最后我们新建一个测试类 TestWeather.java 来测试一下。
 
package observer;public class TestWeather {public static void main(String[] args) {WeatherData weatherData = new WeatherData();//天气数据即被观察者WeatherDisplay weatherDisplay = new WeatherDisplay(weatherData);//天气展示即观察者weatherData.setMessurements(37.2f);//被观察者数据发生变化了,其内部会通知观察者weatherDisplay.display();//查看观察者是否获取到了最新温度数据}}
package observer; public class TestWeather {     public static void main(String[] args) {         WeatherData weatherData = new WeatherData();//天气数据即被观察者         WeatherDisplay weatherDisplay = new WeatherDisplay(weatherData);//天气展示即观察者         weatherData.setMessurements(37.2f);//被观察者数据发生变化了,其内部会通知观察者         weatherDisplay.display();//查看观察者是否获取到了最新温度数据     } } 
现在我们需要验证一下结果,先执行 javac observer/*.java 命令进行编译。然后再执行 java observer.TestWeather 命令运行测试类(大家一定要自己动手运行哦,只有自己实际去运行了才会更能体会其中的思想)。
可以看到,我们只是设置了 WeatherData 对象中的温度,但是 WeatherDisplay 中也实时改变了温度,这就是观察者模式。但是我们这里是观察者主动推的数据给观察者,也就是 push 模式,这样不论观察者是不是需要数据,都会被推送,那么假如数据信息很多,但是我只要其中一种呢?能不能只是观察者自己去拿数据,这是可以的,这就是接下来我们要介绍的观察者模式的另一种写法,也就是 pull 写法。
pull 模式示例
在 JDK 中自带了观察者模式的写法,下面我们就利用 JDK 自带的观察者模式来实现一个 pull 类型的观察者模式。
比如我们以空间好友自行去获取好友发表在空间的动态来举例说明,看看到底该怎么写 pull 类型的观察者模式。
- 新建一个空间动态类 Trends.java 来记录动态信息。
 
package observer;public class Trends {private String nickName;//发表动态的用户昵称private String content;//发表的动态内容public String getNickName() {return nickName;}public void setNickName(String nickName) {this.nickName = nickName;}public String getContent() {return content;}public void setContent(String content) {this.content = content;}}
package observer; public class Trends { private String nickName;//发表动态的用户昵称 private String content;//发表的动态内容 public String getNickName() { return nickName; } public void setNickName(String nickName) { this.nickName = nickName; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } }
- 新建一个空间 Zone.java 类(被观察者),继承 JDK 自带的被观察者对象:Observable。
 
package observer;import java.util.Observable;public class Zone extends Observable {//发表动态public void publishTrends(Trends trends){System.out.println(trends.getNickName() + "发表了一个动态【" + trends.getContent() + "】" );setChanged();//占位,只是设置一个标记说明数据改变了notifyObservers(trends);//通知所有观察者}}
package observer; import java.util.Observable; public class Zone extends Observable { //发表动态 public void publishTrends(Trends trends){ System.out.println(trends.getNickName() + “发表了一个动态【” + trends.getContent() + “】” ); setChanged();//占位,只是设置一个标记说明数据改变了 notifyObservers(trends);//通知所有观察者 } }
- 新建一个好友 Friends.java 类(观察者),实现 JDK 自带的观察者接口:Observer。
 
package observer;import java.util.Observable;import java.util.Observer;public class Friends implements Observer {private String name;//看动态的人名public String getName() {return name;}public void setName(String name) {this.name = name;}@Overridepublic void update(Observable o, Object arg) {//获取(空间)被观察者数据Trends trends = new Trends();if(null != arg && arg instanceof Trends){trends = (Trends)arg;}System.out.println(this.getName() + ",您好!您收到了来自" + trends.getNickName() +"的一条动态【" + trends.getContent() + "】" + "快去点赞吧!");}}
package observer; import java.util.Observable; import java.util.Observer; public class Friends implements Observer { private String name;//看动态的人名 public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public void update(Observable o, Object arg) {//获取(空间)被观察者数据 Trends trends = new Trends(); if(null != arg && arg instanceof Trends){ trends = (Trends)arg; } System.out.println(this.getName() + “,您好!您收到了来自” + trends.getNickName() + “的一条动态【” + trends.getContent() + “】” + “快去点赞吧!”); } }
- 最后我们新建一个测试类 TestObserver.java 来测试一下。
 
package observer;public class TestObserver {public static void main(String[] args) {Zone zone = new Zone();Friends friends = new Friends();//观察者,即查看好友动态的人friends.setName("张三丰");Trends trends = new Trends();//被观察者,即发送动态的人trends.setNickName("张无忌");trends.setContent("祝太师傅长命百岁!");zone.addObserver(friends);//注册观察者zone.publishTrends(trends);//发布动态}}
package observer; public class TestObserver {     public static void main(String[] args) {         Zone zone = new Zone();         Friends friends = new Friends();//观察者,即查看好友动态的人         friends.setName(“张三丰”);         Trends trends = new Trends();//被观察者,即发送动态的人         trends.setNickName(“张无忌”);         trends.setContent(“祝太师傅长命百岁!”);         zone.addObserver(friends);//注册观察者         zone.publishTrends(trends);//发布动态     } } 
重新执行 javac observer/*.java 命令进行编译。然后再执行 java observer.TestObserver 命令运行测试类(大家一定要自己动手运行哦,只有自己实际去运行了才会更能体会其中的思想)。
可以看到,当动态发布之后,好友立即就收到了动态,而这里我们是主动去获取数据的,即 Friends 类的 update 方法。我们可以根据自己的需求想要什么数据就拿什么数据,其它不关心的数据一律不要。
JDK 自带的观察者模式的局限性
JDK 自带的观察者模式虽然可以主动去 pull 自己需要的数据,但也同时存在以下两个问题:
- Observable 是一个类而不是一个接口,所以复用性就不是很好,如果某类同时想具有 Observable 类和另一个超类的行为,就会有问题,毕竟 Java 不支持多继承。
 - Observable 将关键的方法保护起来了,比如 setChanged 方法,也就是我们只能继承 Observable 类才能实现想要的功能,这个违反了合成复用原则。
观察者模式适用场景
观察者模式一般适用于需要实时监听数据的场景,比如事件监听器等。观察者模式的优点
观察者和被观察者之间建立了一个抽象的耦合,要扩展观察者只需要新建观察者并注册进去就可以,扩展性好。观察者模式的缺点
观察者之间有过多的细节依赖、提高时间消耗及程序的复杂度,此外,当被观察者对象过多时,会使得系统非常复杂难以维护。 
