《Head First Design Patterns》—The Observer Pattern
引言 报纸订阅 可以从报纸或杂志的订阅方式去了解观察者模式是什么
报纸或杂志的订阅方式:
从上面的订阅方式可以这样认为观察者模式:
Publishers + Subscribers = Observer Pattern 报纸出版商 + 订阅者 = 观察者模式
we call the publisher the SUBJECT and the subscribers the OBSERVERS . 报纸出版商称为SUBJECT(目标),订阅者称为OBSERVERS(观察者)
继续解释 开始时
状态改变时
观察者模式 通过上面例子已经生动形象得展示了什么是观察者模式
定义 The Observer Pattern defines a one-to-many dependency between objects so that when one object changes state, all of its dependents are notified and updated automatically.
观察者模式 定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
结合定义与之前的例子
Subject和Observer定义了一对多的关系。Observer依赖于此Subject,只要Subject状态一有变化,Observer就会被通知。根据通知的风格,Observer可能因为值的更新而更新。 实现观察者模式的方法不只一种,但是以包含Subject与Observer接口的类设计的做法最常见。
类图
观察者模式提供了一种对象设计,让Subject和Observer之间松耦合。
当两个对象之间松耦合,它们依然可以交互,但是不太清楚彼此的细节。
为什么可以松耦合?
关于Observer的一切,Subject只知道Observer实现了某个接口(也就是Observer接口) 。Subject不需要知道Observer的具体类是谁、做了些什么或其他任何细节。
任何时候我们都可以增加新的Observer 。因为Subject唯一依赖的东西是一个实现Observer接口的对象列表,所以我们可以随时增加Observer。事实上,在运行时我们可以用新的Observer取代现有的Observer,Subject不会受到任何影响。同样的,也可以在任何时候删除某些Observer。
有新类型的Observer出现时,Subject的代码不需要修改 。假如我们有个新的具体类需要当Observer,我们不需要为了兼容新类型而修改Subject的代码,所有要做的就是在新的类里实现此Observer接口,然后注册为Observer即可。Subject不在乎别的,它只会发送通知给所有实现了Observer接口的对象。
我们可以独立地复用Subject或Observer 。如果我们在其他地方需要使用Subject或Observer,可以轻易地复用,因为二者并非紧耦合。
改变Subject或Observer其中一方,并不会影响另一方。因为两者是松耦合的,所以只要他们之间的接口仍被遵守,我们就可以自由地改变他们。
气象监测应用 情况介绍
实现
Subject接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public interface Subject { public void regeisterObserver (Observer o) ; public void removeObserver (Observer o) ; public void notifyObservers () ; }
Observer接口 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public interface Observer { public void update (float temp, float humidity, float pressure) ; }
DisplayElement接口 1 2 3 4 5 6 7 8 9 10 public interface DisplayElement { public void display () ; }
WeatherData 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 import java.util.ArrayList;public class WeatherData implements Subject { private ArrayList<Observer> observers; private float temperature; private float humidity; private float pressure; public WeatherData () { observers = new ArrayList <Observer>(); } public void registerObserver (Observer o) { observers.add(o); } public void removeObserver (Observer o) { int i = observers.indexOf(o); if (i >= 0 ) { observers.remove(i); } } public void notifyObservers () { for (Observer observer : observers) { observer.update(temperature, humidity, pressure); } } public void measurementsChanged () { notifyObservers(); } public void setMeasurements (float temperature, float humidity, float pressure) { this .temperature = temperature; this .humidity = humidity; this .pressure = pressure; measurementsChanged(); } public float getTemperature () { return temperature; } public float getHumidity () { return humidity; } public float getPressure () { return pressure; } }
布告板 CurrentConditionsDisplay 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public class CurrentConditionsDisplay implements Observer , DisplayElement { private float temperature; private float humidity; private Subject weatherData; public CurrentConditionsDisplay (Subject weatherData) { this .weatherData = weatherData; weatherData.registerObserver(this ); } public void update (float temperature, float humidity, float pressure) { this .temperature = temperature; this .humidity = humidity; display(); } public void display () { System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity" ); } }
StatisticsDisplay 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 public class StatisticsDisplay implements Observer , DisplayElement { private float maxTemp = 0.0f ; private float minTemp = 200 ; private float tempSum= 0.0f ; private int numReadings; private WeatherData weatherData; public StatisticsDisplay (WeatherData weatherData) { this .weatherData = weatherData; weatherData.registerObserver(this ); } public void update (float temp, float humidity, float pressure) { tempSum += temp; numReadings++; if (temp > maxTemp) { maxTemp = temp; } if (temp < minTemp) { minTemp = temp; } display(); } public void display () { System.out.println("Avg/Max/Min temperature = " + (tempSum / numReadings) + "/" + maxTemp + "/" + minTemp); } }
测试 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class WeatherStation { public static void main (String[] args) { WeatherData weatherData = new WeatherData (); CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay (weatherData); StatisticsDisplay statisticsDisplay = new StatisticsDisplay (weatherData); weatherData.setMeasurements(80 , 65 , 30.4f ); weatherData.setMeasurements(82 , 70 , 29.2f ); weatherData.setMeasurements(78 , 90 , 29.2f ); } }
输出:
1 2 3 4 5 6 Current conditions: 80.0F degrees and 65.0 % humidity Avg/Max/Min temperature = 80.0 /80.0 /80.0 Current conditions: 82.0F degrees and 70.0 % humidity Avg/Max/Min temperature = 81.0 /82.0 /80.0 Current conditions: 78.0F degrees and 90.0 % humidity Avg/Max/Min temperature = 80.0 /82.0 /78.0
JDK内置实现 Implementation with Observer 首先需要说明的是java.util.observer已经在jdk9中被弃用了:
[JDK-8154801] deprecate Observer and Observable - Java Bug System
Implementation with PropertyChangeListener Observer is deprecated in Java 9. What should we use instead of it? - Stack Overflow
stackoverflow相关讨论指出:我们可以使用 PropertyChangeEvent and PropertyChangeListener 实现
WeacherData 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 package jdk9;import java.beans.PropertyChangeListener;import java.beans.PropertyChangeSupport;public class WeatherData { private float temperature; private float humidity; private float pressure; private PropertyChangeSupport support; public WeatherData () { support = new PropertyChangeSupport (this ); } public void addPropertyChangeListener (PropertyChangeListener pcl) { support.addPropertyChangeListener(pcl); } public void removePropertyChangeListener (PropertyChangeListener pcl) { support.removePropertyChangeListener(pcl); } public void setMeasurements (float temperature, float humidity, float pressure) { support.firePropertyChange("temperature" , this .temperature, temperature); support.firePropertyChange("humidity" , this .humidity, humidity); support.firePropertyChange("pressure" , this .pressure, pressure); this .temperature = temperature; this .humidity = humidity; this .pressure = pressure; } public float getTemperature () { return temperature; } public float getHumidity () { return humidity; } public float getPressure () { return pressure; } }
CurrentConditionsDisplay 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package jdk9;import java.beans.PropertyChangeEvent;import java.beans.PropertyChangeListener;public class CurrentConditionsDisplay implements PropertyChangeListener { private float temperature; private float humidity; private float pressure; @Override public void propertyChange (PropertyChangeEvent evt) { this .temperature = (float ) evt.getNewValue(); this .humidity = (float ) evt.getNewValue(); this .pressure = (float ) evt.getNewValue(); display(); } public void display () { System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity" ); } }
WeatherStation 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 package jdk9;public class WeatherStation { public static void main (String[] args) { WeatherData weatherData = new WeatherData (); CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay (); weatherData.addPropertyChangeListener(currentDisplay); weatherData.setMeasurements(78 , 90 , 29.2f ); } }
观察者模式与MVC 模型 (Model),视图 (View)和控制器 (Controller)
模型 可对应于观察者模式中的Subject,而视图 对应于Observer,控制器 可充当两者之间的中介者
当模型层 的数据发生改变时,视图层 将自动改变其显示内容
总结 优点
可以实现表示层和数据逻辑层的分离
在观察目标和观察者之间建立一个抽象的耦合
支持广播通信,简化了一对多系统设计的难度
符合开闭原则 ,增加新的具体观察者无须修改原有系统代码,在具体观察者与观察目标之间不存在关联关系的情况下,增加新的观察目标也很方便
缺点
将所有的观察者都通知到会花费很多时间
如果存在循环依赖 时可能导致系统崩溃
没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的 ,而只是知道观察目标发生了变化
适用环境
一个抽象模型有两个方面,其中一个方面依赖于另一个方面 ,将这两个方面封装在独立的对象中使它们可以各自独立地改变和复用
一个对象的改变将导致一个或多个其他对象发生改变,且并不知道具体有多少对象将发生改变,也不知道这些对象是谁
需要在系统中创建一个触发链