本章要点:
1.观察者模式定义了对象之间一对多的关系。
2.主题(也就是可观察者)用一个共同的接口来更新观察者。
3.观察者和可观察者之间用松耦合方式结合(loosecoupling),可观察者不知道观察者的细节,只知道观察者实现了观察者接口。
4.使用此模式时,你可从被观察者处推(push)或拉(pull)数据(然而,推得方式被认为更“正确”)。
5.有多个观察者时,不可以依赖特定的通知次序。
6.Java有多重观察者模式的实现,包括了通用的java.util.Observable。
7.要注意java.util.Observable实现上所带来的一些问题。
8.如果有必要的话,可以实现自己的Observable,这并不难,不要害怕。
9.Swing大量使用观察者模式,许多GUI框架也是如此。
10.此模式也被应用在许多地方,例如:JavaBeans、RMI。
----------------------------------------分割线 ----------------------------------------
观察者模式类图:
下面是实例:
现在要做一个气象监测系统,客户提供了一个WeatherData对象(追踪来自气象站的数据,并更新布告板)。我们的工作就是建立一个应用,利用WeatherData对象取得数据,并更新N个布告板。
● WeatherData类具有getter方法,可以取得三个测量值:温度、湿度和气压。
● 当新的测量数据备妥时,measurementsChanged()方法就会被调用(我们不在乎此方法是如何被调用的,我们只在乎它 被调用了)。
● 我们需要实现三个使用天气数据的布告板:“目前状况”、“气象统计”和“天气预报”。一旦WeatherData有新的测量,这些布告必须马上更新。
● 此系统必须可扩展,让其他开发人员建立定制的布告板,用户可以随心所欲地添加或删除任何布告板。
此系统可以用观察者模式实现,下面先从建立接口开始吧:
public interface Subject {
//这两个方法都需要一个观察者作为变量,该观察者是用来注册或被删除的。
public void registerObserver(Observer o);
public void removeObserver(Observer o);
//当主题状态被改变时,这个方法会被调用,以通知所有的观察者。
public void notifyObservers();
}
public interface Observer {
//当气象观测值改变时,主题会把状态值当做方法的参数,传给观察者。Weather对象中包含温度、湿度与气压。
public void update(Weather weather);
}
public interface DisplayElement {
//DisplayElement接口只包含了一个方法,当布告板需要显示时,调用此方法。
public void display();
}
实现WeatherData类(主题)
public class WeatherData implements Subject {
//加上一个list来记录观察者,此list在构造器中建立的。
private List<Observer> observers;
private Weather weather;
public WeatherData() {
observers = new ArrayList<Observer>();
}
//当注册观察者时,我们只要把它加到list即可。
public void registerObserver(Observer o) {
observers.add(o);
}
//同样的,当观察者想取消注册,我们把它从list中删除即可。
public void removeObserver(Observer o) {
int i = observers.indexOf(o);
if(i>0)
observers.remove(i);
}
//在这里,我们把状态告诉每一个观察者。因为观察者都实现了update(),所以我们知道如何通知他们。
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(weather);
}
}
//当从气象站得到更新观测值时,我们通知观察者。
public void measurementsChanged() {
notifyObservers();
}
public void setMeasurements(Weather w) {
this.weather = w;
measurementsChanged();
}
}
建立布告板(观察者)
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private Weather weather;
private Subject weatherData;
//构造器需要weatherData对象(主题)作为注册之用。
public CurrentConditionsDisplay(Subject weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
public void update(Weather weather) {
//当update()被调用时,我们把温度和湿度保存起来,然后调用display()。
this.weather = weather;
display();
}
public void display() {
//display()方法就只是把最近的温度和湿度显示出来。
System.out.println("Current conditions:"+weather.getTemp()
+"F degrees and "+weather.getHumidity()+"% humidity");
}
}
编写测试类
public class App {
public static void main(String[] args) {
//首先,建立一个WeatherData对象(主题)
WeatherData weatherData = new WeatherData();
//建立布告板,并把WeatherData对象传给它
CurrentConditionsDisplay conditionsDisplay =
new CurrentConditionsDisplay(weatherData);
//模拟数据
Weather weather1 = new Weather(80, 65, 30.4f);
Weather weather2 = new Weather(82, 70, 29.2f);
//新的气象测量
weatherData.setMeasurements(weather1);
weatherData.setMeasurements(weather2);
}
}
输出结果:Current conditions:80.0F degrees and 65.0% humidity
Current conditions:82.0F degrees and 70.0% humidity
首先,把WeatherData改成使用java.util.Observable
/**
* 我们现在正在继承Observable
* 我们不再需要追踪观察者了,也不需要管理注册与删除(让超类代劳即可)
* 所以我们把注册、添加、通知的相关代码删除
*/
public class WeatherData extends Observable {
private Weather weather;
//我们的构造器不再需要为了记住观察者们而建立数据结构了
public WeatherData() {
}
//当从气象站得到更新观测值时,我们通知观察者
public void measurementsChanged() {
//在调用notifyObservers()之前,要先调用setChanged()来指示状态已经改变
setChanged();
//我们没有调用notifyObservers()传送数据对象,这表示我们采用"拉"
notifyObservers();
}
public Weather getWeather() {
return weather;
}
public void setMeasurements(Weather w) {
this.weather = w;
measurementsChanged();
}
现在,让我们重做
CurrentConditionsDisplay
//我们现在正在实现java.util.Observer接口
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private Weather weather;
private Observable observable;
//现在构造器需要一个Observable当参数,并将CurrentConditionsDisplay对象登记成为观察者
public CurrentConditionsDisplay(Observable observable) {
this.observable = observable;
observable.addObserver(this);
}
//改变update()方法,增加Observable和数据对象作为参数
public void update(Observable obs, Object arg) {
//在update()中,先确定可观察者属于WeatherData类型
if(obs instanceof WeatherData){
WeatherData weatherData = (WeatherData) obs;
this.weather = weatherData.getWeather();
display();
}
}
public void display() {
System.out.println("Current conditions:"+weather.getTemp()
+"F degrees and "+weather.getHumidity()+"% humidity");
}
}
细心的同学可能已经发现了一个问题,java.util.Observable是一个“类”而不是一个“接口”,更糟的是,它甚至没有实现一个接口。如果某类想同时具有
Observable类另一个超类的行为,就会陷入两难,毕竟Java不支持多继承。如果你能够扩展java.util.Observable,那么Observable可能可以符合你的需求。否则,你可能需要像我们开头的做法那样自己实现这一整套观察者模式,尽管它并不是很复杂。
现在来总结一下,一个新的设计原则:
为了交互对象之间的松耦合设计而努力。