观察者模式(Observer)
——在对象之间定义一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象都会收到通知,并自动更新。
(出版者Subject+订阅者Observer=观察者模式)
- 特点:定义并维护对象之间的一对多关系
- 原则:为交互对象之间的松耦合设计而努力
- 示例(气象站类图)
三个接口:
1 public interface Subject{ 2 public void registerObserver(Observer o);//注册观察者 3 public void removeOberver(Observer o);//删除观察者 4 public void notifyObserver();//通知观察者 5 } 6 7 public interface Observer{ 8 public void update(float temp, float humidity, float pressure); 9 } 10 11 public interface DisplayElement{ 12 public void display(); 13 }
在WeatherData中实现主题接口:
1 public class WeatherData implements Subject{ 2 private ArrayList observers;//记录观察者 3 private float temperature; 4 private float humidity; 5 private float pressure; 6 7 public WeatherData(){ 8 observers = new ArrayList(); 9 } 10 11 public void registerObserver(Observer o){//注册观察者 12 observers.add(o); 13 } 14 public void removeOberver(Observer o){//删除观察者 15 int i = observers.indexOf(o); 16 if(i >= 0){ 17 observers.remove(0); 18 } 19 } 20 public void notifyObservers(){ 21 for(int i = 0; i < observers.size(); i++){ //通知每一个观察者 22 Observer observer = (Observer)observers.get(i); 23 observer.update(temperature, humidity, pressure); 24 } 25 } 26 public void measurementsChanged(){ 27 notifyObservers(); 28 } 29 public void setMeasurements(float temp, float hum, float pre){ 30 this.temperature = temp; 31 this.humidity = hum; 32 this.pressure = pre; 33 measurementsChanged(); 34 } 35 }
实现CurrentConditionDisplay.java
1 public class CurrentConditionsDisplay implements Observer, DisplayElement{ 2 private float temperature; 3 private float humidity; 4 private Subject weatherData; 5 6 public CurrentConditionsDisplay(Subject weatherData){//构造器需要weatherData对象作为注册之用 7 this.weatherData = weatherData; 8 weatherData.registerObserver(this); 9 } 10 public void update(float temp, float humidity, float pressure){ 11 this.temperature = temp; 12 this.humidity = humidity; 13 display(); 14 } 15 public void display(){ 16 //输出 17 } 18 }
测试程序:
1 public class WeatherStation { 2 3 public static void main(String[] args) { 4 WeatherData weatherData = new WeatherData();//建立weatherData对象 5 6 CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData); 7 StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData); 8 ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);//将对象传给三个Observer,即add观察者 9 10 weatherData.setMeasurements(80, 65, 30.4f); 11 weatherData.setMeasurements(82, 70, 29.2f); 12 weatherData.setMeasurements(78, 90, 29.2f); 13 } 14 }
Java内置的观察者模式
上述示例实现信息由Subject “推送(push)” 至Observer,使用Java内置的观察者模式可以使用推(push)或拉(pull)的方式传送数据。不同的是WeatherData现在扩展自Observable类,并继承到一些add、delete、notify观察者的方法。
Subject -> java.util.Observable(类)
Observer -> java.util.Observer(接口)
Observable如何送出通知?
首先需要利用扩展 java.util.Observable 接口产生“可观察者”类(想要进行通知,则必须调用Observable类的setChanged方法,但是Observable的setChanged方法为protected,故只能使用继承来实现自己的主题对象),然后:
- 调用setChanged(),标记状态已经改变的事实;
- 调用notifyObservers()中的一个:notifyObservers() 或 notifyObservers(Object arg)
Observer如何接收通知?
update(Observable o, Object arg) :主题Observable作为第一变量,好让观察者知道是哪个主题通知它的。Object arg正是传入notifyObservers(Object arg)的数据对象,如果没有说明则为空。
若想push数据给观察者,可以把数据作数据对象传送给notifyObservers(Object arg)方法。否则,观察者就必须从可观察者对象中pull数据。如何拉数据?
WeatherData.java
1 public class WeatherData extends Observable{ 2 private float temperature; 3 private float humidity; 4 private float pressure; 5 6 public WeatherData(){ } //无需建立观察者列表ArrayList了 7 public void measurementsChanged(){ 8 setChanged();//状态已经改变 9 notifyObservers();//pull 10 } 11 public void setMeasurements(float temp, float hum, float pre){ 12 this.temperature = temp; 13 this.humidity = hum; 14 this.pressure = pre; 15 measurementsChanged(); 16 } 17 public float getTemperature(){ 18 return temperature; 19 } 20 public float gethumidity(){ 21 return humidity; 22 } 23 public float getpressure(){ 24 return pressure; 25 } 26 }
CurrentConditionDisplay.java
1 public class CurrentConditionsDisplay implements Observer, DisplayElement{ 2 private float temperature; 3 private float humidity; 4 Observable observable; 5 6 public CurrentConditionsDisplay(Observable observable){ 7 this.observable = observable; 8 observable.addObserver(this); 9 } 10 public void update(Observable o, Object arg){ 11 if(o instanceof WeatherData){ 12 WeatherData weatherData = (WeatherData)o; 13 this.temperature = weatherData.getTemperature(); 14 this.humidity = weatherData.gethumidity(); 15 display(); 16 } 17 } 18 public void display(){ 19 //输出 20 } 21 }
notice:
- 不想推送的时候,不调用setChanged()方法即可
- 通知顺序不依赖于注册的顺序(即主题通知观察者的顺序与添加观察者的顺序无关)
- setChanged()方法的必要性:若无,则温度计读数每十分之一度就会更新,造成WeatherData对象持续不断地通知观察者。若希望温差达到半度时更新,就调用setChanged()。有更多的弹性,更适当地通知观察者。
使用Java自带的观察者模式的缺点:
- Observable是一个类,而不是一个接口,导致Observable类的扩展性不高,不如自己实现的观察者模式灵活
- Observable将某些方法保护了起来(setChanged()和clearChanged()为protected),这意味着除非继承自Observable,否则将有关键的方法不能调用。导致无法通过组合的方式使其它类获得Observable类的功能。违反了设计原则“多用组合,少用继承”。