观察者模式:是JDK中使用最多的模式之一,它定义了对象之间的一对多的依赖关系,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。其中,将有状态的对象叫做主题Subject ,将它的所有依赖者叫做依赖者对象,又叫做观察者Observer,观察者模式的代表是MVC,以后会再单独介绍。
如下图所示:
使用类图的方法定义观察者模式,如下图所示:
从类图可知,观察者模式的几个主要角色:
抽象主题:保存所有观察者的引用,可以增加和删除观察者对象
具体主题:当具体主题的状态发生变化的时候,通知给所有的观察者
抽象观察者:所有观察者的接口,当主题的状态发生变化的时候,会调用其update方法
具体观察者:实现观察者接口,以保证在主题的状态发生变化的时候,可接收更新
两个对象之间的松耦合的设计,将对象之间的相互依赖降到了最低,能够建立有弹性的OO系统,应对变化
观察者模式是如何实现这一设计的:
对于主题来说,主题只知道观察值实现了Observer接口,不需要知道观察者的具体实现类是谁,做了什么以及具体的实现细节,主题对象唯一依赖的东西是一个实现了Observer接口的对象列表,所以,可以在运行时随时增加新的观察者,删除不需要的观察者
对于观察者来说,当增加一个新的具体类的时候,不需要修改主题的代码,只需在新的观察者的类中实现观察者的接口,将其注册为观察者即可
以实现气象站举例说明观察者模式的使用方法:
背景说明:建立一个应用,通过使用weatherData对象追踪目前的天气状况,并分别在三种布告板上显示目前的状况,气象统计以及简单的预报,且布告板是可扩展的
1 import java.util.ArrayList; 2 3 /** 4 * 气象属性VO 5 */ 6 class WeatherModel{ 7 private float temperature; 8 private float humidity; 9 private float pressure; 10 public WeatherModel(){} 11 public WeatherModel(float temperature, float humidity, float pressure) { 12 this.temperature = temperature; 13 this.humidity = humidity; 14 this.pressure = pressure; 15 } 16 public float getTemperature() { 17 return temperature; 18 } 19 public void setTemperature(float temperature) { 20 this.temperature = temperature; 21 } 22 public float getHumidity() { 23 return humidity; 24 } 25 public void setHumidity(float humidity) { 26 this.humidity = humidity; 27 } 28 public float getPressure() { 29 return pressure; 30 } 31 public void setPressure(float pressure) { 32 this.pressure = pressure; 33 } 34 } 35 /** 36 * 主题接口 37 */ 38 interface Subject{ 39 public void registerObserver(Observer o); //注册观察者 40 public void removeObserver(Observer o); //删除观察者 41 public void notifyObserver(); //主题状态发生改变时,调用该方法,通知所有的观察者 42 } 43 /** 44 * 观察者接口 45 * 46 */ 47 interface Observer{ 48 //更新天气状况 49 public void update(WeatherModel weatherModel1); 50 } 51 52 interface DisplayElement{ 53 //布告板显示 54 public void display(); 55 } 56 /** 57 * 天气数据类,实现主题对象接口 58 * 59 */ 60 class WeatherData implements Subject{ 61 //存放所有观察者list 62 private ArrayList<Observer> observers = new ArrayList<Observer>(); 63 private WeatherModel weatherModel = new WeatherModel() ; 64 @Override 65 public void registerObserver(Observer o) { 66 observers.add(o); 67 } 68 @Override 69 public void removeObserver(Observer o) { 70 int i = observers.indexOf(o); 71 if (i>0) { 72 observers.remove(i); 73 } 74 } 75 @Override 76 public void notifyObserver() { 77 for (int i = 0; i < observers.size(); i++) { 78 Observer observer = (Observer) observers.get(i); 79 observer.update(weatherModel); 80 } 81 82 } 83 84 //天气数据改变时,保存改变的数据,再通知给所有的观察者 85 public void setMeasurements(WeatherModel weatherModel1){ 86 87 weatherModel.setTemperature(weatherModel1.getTemperature()); 88 weatherModel.setHumidity(weatherModel1.getHumidity()); 89 weatherModel.setPressure(weatherModel1.getPressure()); 90 notifyObserver(); 91 } 92 93 } 94 /** 95 * 目前天气状况的布告板 ,实现观察者,显示的接口 96 * 97 */ 98 class CurrentConditionsDisplay implements Observer,DisplayElement{ 99 private WeatherModel weatherModel = new WeatherModel() ; 100 private Subject weatherData; 101 102 //构造方法中需要weatherdata对象,以在主题对象中注册观察者 103 public CurrentConditionsDisplay(Subject weatherData1) { 104 this.weatherData = weatherData1; 105 weatherData.registerObserver(this); 106 107 } 108 @Override 109 public void display() { 110 System.out.println("Current Condition:" + weatherModel.getTemperature() + "F degrees and " + weatherModel.getHumidity() + "% humidity"); 111 112 } 113 114 @Override 115 public void update(WeatherModel weatherModel1) { 116 weatherModel.setTemperature(weatherModel1.getTemperature()); 117 weatherModel.setHumidity(weatherModel1.getHumidity()); 118 display(); 119 } 120 121 } 122 /** 123 * 测试类 124 * 125 */ 126 public class ObserverTest { 127 128 public static void main(String[] args) { 129 130 WeatherData weatherData = new WeatherData(); 131 132 WeatherModel weatherModel = new WeatherModel(80, 65, 30.4f); 133 WeatherModel weatherModel2 = new WeatherModel(82, 70, 29.2f); 134 135 CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData); 136 137 weatherData.setMeasurements(weatherModel); 138 weatherData.setMeasurements(weatherModel2); 139 140 } 141 142 }
运行结果:
观察者模式是如何遵循以下设计原则的:
1、找出程序中变化的方面,然后将其和固定不变的方面相分离:
在观察者模式中,主题的状态时会变化的,观察者的数目和类型也是会发生变化的,由于两者是分开来的,主题状态的变化不会影响观察者,观察者的变化也不会影响主题;
2、针对接口编程,不针对实现编程:
主题和观察者都使用接口,观察者利用主题的接口进行注册,主题利用观察者的接口进行通知;
3、多用组合,少用继承:
利用组合将观察者组合进主题对象中,对象之间的关系不是继承产生的,而是在运行时利用组合产生的;
Java API有内置的观察者模式,与上述实现的有一点差异,以气象站为例说明使用如何使用Java API内置的观察者模式完成开发:
1、具体观察者对象实现观察者接口,调用addObserver()方法,将对象变成观察者,不想当观察者时,调用deleteObserver()方法;
2、具体主题对象继承java.util.Observable类,这里,主题对象又叫做可观察者,然后,调用setChanged()方法,标记状态已经改变的事实,最后调用两种notifyObservers()方法中的一个:notifyObservers() 或者 notifyObservers(Object arg),当可观察者想主动把数据推给观察者时,可以把数据传送给notifyObservers(Object arg)方法,否则,观察者必须从可观察者中拉数据
3、观察者接收通知,实现update方法,update(Observable obs,Object args),其中第一个变量为主题本身,方便让观察者知道是哪一个主题通知它,第二个变量是数据对象,没有则为空
1 package designPattern; 2 import java.util.Observable; 3 import java.util.Observer; 4 5 /** 6 * 气象属性VO 7 */ 8 class WeatherModel{ 9 private float temperature; 10 private float humidity; 11 private float pressure; 12 public WeatherModel(){} 13 public WeatherModel(float temperature, float humidity, float pressure) { 14 this.temperature = temperature; 15 this.humidity = humidity; 16 this.pressure = pressure; 17 } 18 public float getTemperature() { 19 return temperature; 20 } 21 public void setTemperature(float temperature) { 22 this.temperature = temperature; 23 } 24 public float getHumidity() { 25 return humidity; 26 } 27 public void setHumidity(float humidity) { 28 this.humidity = humidity; 29 } 30 public float getPressure() { 31 return pressure; 32 } 33 public void setPressure(float pressure) { 34 this.pressure = pressure; 35 } 36 } 37 38 interface DisplayElement{ 39 //布告板显示 40 public void display(); 41 } 42 /** 43 * 天气数据类,继承Observable类 44 * 45 */ 46 class WeatherData extends Observable{ 47 private WeatherModel weatherModel = new WeatherModel() ; 48 49 public void measurementsChanged(){ 50 //标记状态改变的事实 51 setChanged(); 52 //使用的是无参数的方法,说明是观察者从可观察者中拉数据,不是可观察者主动发生数据给观察者 53 notifyObservers(); 54 } 55 //天气数据改变时,保存改变的数据,再通知给所有的观察者 56 public void setMeasurements(WeatherModel weatherModel1){ 57 58 weatherModel.setTemperature(weatherModel1.getTemperature()); 59 weatherModel.setHumidity(weatherModel1.getHumidity()); 60 weatherModel.setPressure(weatherModel1.getPressure()); 61 measurementsChanged(); 62 } 63 64 //观察者会利用get方法取得可观察者的状态 65 public WeatherModel getWeatherModel() { 66 return weatherModel; 67 } 68 public void setWeatherModel(WeatherModel weatherModel) { 69 this.weatherModel = weatherModel; 70 } 71 72 } 73 /** 74 * 目前天气状况的布告板 ,实现观察者,显示的接口 75 * 76 */ 77 class CurrentConditionsDisplay implements Observer,DisplayElement{ 78 private WeatherModel weatherModel = new WeatherModel() ; 79 Observable observable; 80 81 public CurrentConditionsDisplay(){} 82 83 @Override 84 public void display() { 85 System.out.println("Current Condition:" + weatherModel.getTemperature() + "F degrees and " + weatherModel.getHumidity() + "% humidity"); 86 87 } 88 89 public void update(Observable obs,Object args) { 90 if (obs instanceof WeatherData) { 91 92 WeatherData weatherData = (WeatherData) obs ; 93 weatherModel.setTemperature(weatherData.getWeatherModel().getTemperature()); 94 weatherModel.setHumidity(weatherData.getWeatherModel().getHumidity()); 95 display(); 96 97 } 98 99 } 100 101 } 102 /** 103 * 测试类 104 * 105 */ 106 public class ObserverByJava { 107 108 public static void main(String[] args) { 109 110 WeatherData weatherData = new WeatherData(); 111 112 WeatherModel weatherModel = new WeatherModel(80, 65, 30.4f); 113 WeatherModel weatherModel2 = new WeatherModel(82, 70, 29.2f); 114 115 CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(); 116 //注册观察者 117 weatherData.addObserver(currentConditionsDisplay); 118 119 weatherData.setMeasurements(weatherModel); 120 weatherData.setMeasurements(weatherModel2); 121 122 } 123 124 }
使用Java内置的观察者模式优缺点:
优点:方便使用
缺点:违反了OO设计原则中的“针对接口编程”和“少用继承,多用组合”,因为java.util.Observable是一个类,所以必须设计一个类继承它,如果该类想要同时拥有两个类的行为,就会陷入两难,因为java不支持多种继承,限制了Observable复用的潜力,违反了第一个设计原则;并且通过查看源码可知,setChanged()被定义为protected,意味着只能继承Observable,不能通过组合的方式使用Observable,违反了第二个设计原则。