介绍

观察者模式。观察者顾名思义就是有监听的意思,可以用来实现对象的监听。观察者模式主要有两种写法:一种是监听到消息之后主动获取,另一种就是对方主动推送消息到观察者。

什么是观察者模式


观察者模式(Observer Pattern)也叫做发布订阅模式,其定义了对象之间的一对多依赖,让多个观察者对象同时监听一个主体对象,当主体对象发生变化时,它的所有依赖者(观察者)都会收到通知并更新,观察者模式属于行为型模式。

push 模式示例


下面我们就以获取天气预报中的气温举例进行说明,看看观察者模式应该如何实现(这里我们需要新建一个 observer 目录,相关类创建在 observer 目录下)。

  • 新建一个观察者接口 Observer.java。 ```java package observer;

public interface Observer { void update(float temperature);//更新天气信息 }

  1. package observer; public interface Observer { void update(float temperature);//更新天气信息 }
  2. - 接下来新建一个 Subject.java 类,用来管理观察者,主要定义了三个方法。
  3. ```java
  4. package observer;
  5. public interface Subject {
  6. void registerObserver(Observer o);//注册观察对象
  7. void removeObserver(Observer o);//移除观察对象
  8. void notifyObservers();//通知观察对象
  9. }

package observer; public interface Subject { void registerObserver(Observer o);//注册观察对象 void removeObserver(Observer o);//移除观察对象 void notifyObservers();//通知观察对象 }

  • 接下来再建立一个 WeatherData.java 类,实现 Subject 接口。
  1. package observer;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. public class WeatherData implements Subject {
  5. private List<Observer> observers;//观察者不止一个,所以用list进行维护
  6. private float temperature;//温度
  7. public void setMessurements(float temperature){
  8. this.temperature = temperature;
  9. notifyObservers();//气温信息发生变化时,通知所有观察者
  10. }
  11. public WeatherData() {//初始化list
  12. this.observers = new ArrayList<>();
  13. }
  14. /**
  15. * 注册观察者
  16. * @param o
  17. */
  18. @Override
  19. public void registerObserver(Observer o) {
  20. observers.add(o);
  21. }
  22. /**
  23. * 移除观察者
  24. * @param o
  25. */
  26. @Override
  27. public void removeObserver(Observer o) {
  28. int i = observers.indexOf(o);
  29. if(i >= 0){
  30. observers.remove(i);
  31. }
  32. }
  33. /**
  34. * 通知所有观察者
  35. */
  36. @Override
  37. public void notifyObservers() {
  38. for (Observer observer : observers){//遍历所有观察者
  39. observer.update(temperature);//通知观察者更新数据信息
  40. }
  41. }
  42. }

package observer; import java.util.ArrayList; import java.util.List; public class WeatherData implements Subject { private List observers;//观察者不止一个,所以用list进行维护 private float temperature;//温度 public void setMessurements(float temperature){ this.temperature = temperature; notifyObservers();//气温信息发生变化时,通知所有观察者 } public WeatherData() {//初始化list this.observers = new ArrayList<>(); } / 注册观察者 @param o */ @Override public void registerObserver(Observer o) { observers.add(o); } / 移除观察者 @param o / @Override public void removeObserver(Observer o) { int i = observers.indexOf(o); if(i >= 0){ observers.remove(i); } } /** 通知所有观察者 */ @Override public void notifyObservers() { for (Observer observer : observers){//遍历所有观察者 observer.update(temperature);//通知观察者更新数据信息 } } }
WeatherData 类就相当于是一个被观察者,所以接下来我们还需要一个观察者。

  • 新建一个观察者类 WeatherDisplay.java,需要实现 Observer 接口。
  1. package observer;
  2. public class WeatherDisplay implements Observer {
  3. private Subject subject;//维护观察者
  4. private float temperature;//温度
  5. public WeatherDisplay(Subject subject) {//注册监听对象
  6. this.subject = subject;
  7. subject.registerObserver(this);
  8. }
  9. @Override
  10. public void update(float temperature) {//当被观察者气温发生变化会调用这个方法,也就等于更新了观察者对象的数据
  11. this.temperature = temperature;
  12. }
  13. public void display(){
  14. System.out.println("当前最新的温度为:" + temperature);
  15. }
  16. }

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 来测试一下。
  1. package observer;
  2. public class TestWeather {
  3. public static void main(String[] args) {
  4. WeatherData weatherData = new WeatherData();//天气数据即被观察者
  5. WeatherDisplay weatherDisplay = new WeatherDisplay(weatherData);//天气展示即观察者
  6. weatherData.setMessurements(37.2f);//被观察者数据发生变化了,其内部会通知观察者
  7. weatherDisplay.display();//查看观察者是否获取到了最新温度数据
  8. }
  9. }

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 命令运行测试类(大家一定要自己动手运行哦,只有自己实际去运行了才会更能体会其中的思想)。
观察者模式 - 图1
可以看到,我们只是设置了 WeatherData 对象中的温度,但是 WeatherDisplay 中也实时改变了温度,这就是观察者模式。但是我们这里是观察者主动推的数据给观察者,也就是 push 模式,这样不论观察者是不是需要数据,都会被推送,那么假如数据信息很多,但是我只要其中一种呢?能不能只是观察者自己去拿数据,这是可以的,这就是接下来我们要介绍的观察者模式的另一种写法,也就是 pull 写法。

pull 模式示例


在 JDK 中自带了观察者模式的写法,下面我们就利用 JDK 自带的观察者模式来实现一个 pull 类型的观察者模式。
比如我们以空间好友自行去获取好友发表在空间的动态来举例说明,看看到底该怎么写 pull 类型的观察者模式。

  • 新建一个空间动态类 Trends.java 来记录动态信息。
  1. package observer;
  2. public class Trends {
  3. private String nickName;//发表动态的用户昵称
  4. private String content;//发表的动态内容
  5. public String getNickName() {
  6. return nickName;
  7. }
  8. public void setNickName(String nickName) {
  9. this.nickName = nickName;
  10. }
  11. public String getContent() {
  12. return content;
  13. }
  14. public void setContent(String content) {
  15. this.content = content;
  16. }
  17. }

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。
  1. package observer;
  2. import java.util.Observable;
  3. public class Zone extends Observable {
  4. //发表动态
  5. public void publishTrends(Trends trends){
  6. System.out.println(trends.getNickName() + "发表了一个动态【" + trends.getContent() + "】" );
  7. setChanged();//占位,只是设置一个标记说明数据改变了
  8. notifyObservers(trends);//通知所有观察者
  9. }
  10. }

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。
  1. package observer;
  2. import java.util.Observable;
  3. import java.util.Observer;
  4. public class Friends implements Observer {
  5. private String name;//看动态的人名
  6. public String getName() {
  7. return name;
  8. }
  9. public void setName(String name) {
  10. this.name = name;
  11. }
  12. @Override
  13. public void update(Observable o, Object arg) {//获取(空间)被观察者数据
  14. Trends trends = new Trends();
  15. if(null != arg && arg instanceof Trends){
  16. trends = (Trends)arg;
  17. }
  18. System.out.println(this.getName() + ",您好!您收到了来自" + trends.getNickName() +
  19. "的一条动态【" + trends.getContent() + "】" + "快去点赞吧!");
  20. }
  21. }

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 来测试一下。
  1. package observer;
  2. public class TestObserver {
  3. public static void main(String[] args) {
  4. Zone zone = new Zone();
  5. Friends friends = new Friends();//观察者,即查看好友动态的人
  6. friends.setName("张三丰");
  7. Trends trends = new Trends();//被观察者,即发送动态的人
  8. trends.setNickName("张无忌");
  9. trends.setContent("祝太师傅长命百岁!");
  10. zone.addObserver(friends);//注册观察者
  11. zone.publishTrends(trends);//发布动态
  12. }
  13. }

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 命令运行测试类(大家一定要自己动手运行哦,只有自己实际去运行了才会更能体会其中的思想)。
观察者模式 - 图2
可以看到,当动态发布之后,好友立即就收到了动态,而这里我们是主动去获取数据的,即 Friends 类的 update 方法。我们可以根据自己的需求想要什么数据就拿什么数据,其它不关心的数据一律不要。

JDK 自带的观察者模式的局限性

JDK 自带的观察者模式虽然可以主动去 pull 自己需要的数据,但也同时存在以下两个问题:

  1. Observable 是一个类而不是一个接口,所以复用性就不是很好,如果某类同时想具有 Observable 类和另一个超类的行为,就会有问题,毕竟 Java 不支持多继承。
  2. Observable 将关键的方法保护起来了,比如 setChanged 方法,也就是我们只能继承 Observable 类才能实现想要的功能,这个违反了合成复用原则。

    观察者模式适用场景

    观察者模式一般适用于需要实时监听数据的场景,比如事件监听器等。

    观察者模式的优点

    观察者和被观察者之间建立了一个抽象的耦合,要扩展观察者只需要新建观察者并注册进去就可以,扩展性好。

    观察者模式的缺点

    观察者之间有过多的细节依赖、提高时间消耗及程序的复杂度,此外,当被观察者对象过多时,会使得系统非常复杂难以维护。