观察者模式是JDK中使用最多的模式之一。
气象监测应用:
任务:
WeatherData对象负责追踪目前的天气状况(温度、湿度、气压)。建立一个应用,有三种布告板,分别显示目前的状况、气象统计及简单的预报。当WeatherObject 对象获得最新的测量数据时, 三种布告板必须实时更新。这个应用应当可以扩展,写出一组API,可以让其他开发人员可以写出自己的气象布告板,井插入此应用中。
此系统中的三个部分是气象站(获取实际气象数据的物理装置)、WeatherData对象(追踪来自气象站的数据,并更新布告板)和布告板(显示目前天气状况给用户看)。
如图:
WeatherData对象知道如何跟物理气象站联系,以取得更新的数据。WeatherData对象会随即更新三个布告板的显示: 目前状况(温度、湿度、气压)、气象统计和天气预报。
如果我们选择接受这个项目, 我们的工作就是建立一个应用, 利用WeatherData对象取得数据, 井更新三个布告板:目前状况、气象统计和天气预报。
WeatherData类:
我们目前知道:
WeatherData类具有getter方法,可以取得三个测量值:温度、湿度与气压;
当新的测量数据备妥时,measurementsChanged()方法就会被调用(我们不在乎此方法是如何袚调用的,我们只在乎它被调用了);
我们需要实现三个使用天气数据的布告板: “目前状况“布告、“ 气象统计” 布告、“天气预报”布告。一且WeatherData有新的测量, 这些布告必须马上更新;
此系统必须可扩展,让其他开发入员建立定制的布告板,用户可以随心所欲地添加或删除任何布告板。目前初始的布告板有三类: “ 目前状况“布告、“气象统计“布告、“天气预报“布告。
一个错误的范例:
错误的地方:
观察者模式:主题对象与观察者对象的关系
鸭子对象变为观察者对象:
老鼠对象想从观察者除名:
观察者模式:
观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。观察者模式定义了一系列对象之间的一对多关系。观察者依赖于此主题,只要主题状态一有变化,观察者就会被通知。根据通知的风格,观察者可能因此新值而更新。
如:
实现观察者模式的方法不只一种,但是以包含Subject与Observer接口的类设计的做法最常见。
定义观察者模式:类图
主题是具有状态的对象, 并且可以控制这些状态。因为主题是真正拥有数据的人,观察者是主题的依赖者,在数据变化时更新,这样比起让许多对象控制同一份数据,可以得到更干净的OO设计。
松耦合:
观察者模式提供了一种对象设计, 让主题和观察者之间松耦合。
关于观察者的一切,主题只知道观察者实现了某个接口(也就是Observer接口)。主题不需要知道观察者的具体类是谁、做了些什么或其他任何细节。
任何时候我们都可以增加新的观察者。因为主题唯一依赖的东西是一个实现Observer接口的对象列表,所以我们可以随时增加观察者。同样我们也可以删除观察者。
有新类型的观察者出现时,主题的代码不需要修改。它只会发送通知给所有实现了观察者接口的对象。
改变主题或观察者其中一方,并不会影响另一方。因为两者是松耦合的,所以只要他们之间的接口仍被遵守,我们就可以自由地改变他们。
设计原则:
为了交互对象之间的松耦合设计而努力。松耦合的设计之所以能让我们建立有弹性的OO系统,能够应对变化,是因为对象之间的互相依赖降到了最低。
回到气象站项目:
使用观察者模式,WeatherData类是“主题”,各种布告板是“观察者”。布告板要称为主题的观察者必须先向WeatherData对象注册。一且WeatherData知道有某个布告板的存在,就会适时地调用布告板的某个方法来告诉布告板改变的观测值是多少。
尽管布告板的类都不一样,但是它们都应该实现相同的接口,好让WeatherData对象能够知道如何把观测值送给它们。因此每个布告板都应该有一个update()的方法,供WeatherData对象调用。这个update()方法应该在所有布告板都实现的共同接口里定义。
设计图:
自建Subject接口和Observer接口进行气象站的代码实现:
先建立Subjcet、Observer、DisplayElement接口。
在WeatherData类中实现主题接口。
建立其中一个布告板类。
代码如下:
//subject接口
public interface Subject {
public void registerObserver(Observer o);
public void removeObserver(Observer o);
public void notifyObservers();
}
//Observer接口
public interface Observer {
public void update(float temp, float humidity, float pressure);
}
//DisplayElement接口
public interface DisplayElement {
public void display();
}
//WeatherData类
import java.util.ArrayList;
public class WeatherData implements Subject {
private ArrayList observers;
private float temperature;
private float humidity;
private float pressure;
//类的构造方法
public WeatherData() {
observers = new ArrayList();
}
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 (int i = 0; i < observers.size(); i++) {
Observer observer = (Observer) observers.get(i);
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();
}
}
//CurrentConditionsDisplay类
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);
//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 ");
}
}
//WeatherStation测试类
public class Main {
public static void main(String[] args) {
//创建一个WeatherData类的对象,构造方法创建了一个ArrayList用来存放observer
WeatherData weatherData = new WeatherData();
//创建一个CurrentConditionsDisplay类的对象
//this是类就是CurrentConditionsDisplay的成员对象,构造方法初始化时将赋给this的Subject属性一个值,值就是传入的weatherData对象,然后注册this到ArrayList数组中,此时数组中就有了一个obersver对象
CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
//StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
//ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
//传入更新的数据,setMeasurements会调用measurementsChanged()方法,measurementsChanged()方法又会调用notifyObservers()方法,该方法定义了一个循环,每次从ArryList中取出一个observer对象,然后对其调用update()方法
//又observer对象均是从ArrayList数组中取出,这些对象都是在CurrentConditionsDisplay类构造方法中将CurrentConditionsDisplay类的成员变量注册到ArrayList数组中的,它们都是CurrentConditionsDisplay类的成员变量,故调用update()方法时会调用到CurrentConditionsDisplay类中覆写后的update()方法,update()方法又会调用覆写后的display()方法,这样就会打印出类似如下一行结果:Current conditions: 80.0F degrees and 65.0 % humidity
weatherData.setMeasurements(80, 65, 30.4f);
weatherData.setMeasurements(82, 70, 29.2f);
weatherData.setMeasurements(78, 90, 29.2f);
}
}
运行截图如下:
上面主题向观察者推送消息使用的是“推”的方式。
事实上,对于主题向观察者推送消息的方式有两种:“推”(主题主动)和“拉”(观察者主动)。两种做法都有各自的优点。Java内置的Observer 模式两种做法都支持。
使用Java内置的观察者模式的代码实现:
java. util包(package) 内包含最基本的Observer接口与Observable类。
修改后的设计图:
现在我们的WeatherData (也就是我们的主题)现在扩展自Observable类,并继承一些增加删除、通知观察者的方法(以及具他的方法)。
对象变成观察者:
实现观察者接口(java.uitl.Observer) , 然后调用任何Observable 对象的addObserver()方法。不想冉当观察者时,调用deleteObserver()方法就可以了。
观察者送出通知:
利用扩展java.util.Observable接口产生”可观察者”类。先调用setChanged()方法,标记状态已经改变的事实。然后调用两种notifyObservers()方法中的一个:notifyObservers()或notifyObservers(Object arg)。
观察者接收通知:
update (Observable o,object arg)。第一个参数是主题,第二个参数则是要接收通知的观察者对象。
代码实现:
首先, 把WeatherData改成使用java.util.Observable。
重做CurrentConditionsDispiay。
代码如下:
//DisplayElement接口
public interface DisplayElement {
public void display();
}
//WeatherData类(继承Observable接口)
import java.util.Observable;
import java.util.Observer;
public class WeatherData extends Observable {
private float temperature;
private float humidity;
private float pressure;
//现在用默认构造方法,不再构造一个储存观察者对象的ArrayList
public WeatherData() {
}
public void measurementsChanged() {
//先设置状态改变,再通知观察者
setChanged();
notifyObservers();
}
public void setMeasurements(float temperature, float humidity, float pressure) {
//传入改变的参数
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
//下面3个不是新方法,写出来是为了提醒一下有这些方法
public float getTemperature() {
return temperature;
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
}
//CurrentConditionsDisplay类
import java.util.Observable;
import java.util.Observer;
public class CurrentConditionsDisplay implements Observer, DisplayElement {
Observable observable;
private float temperature;
private float humidity;
//类的构造方法,设置成员变量属性Observable的值为observable,即传入的对象,然后注册添加到观察者队列中
public CurrentConditionsDisplay(Observable observable) {
this.observable = observable;
observable.addObserver(this);
}
public void update(Observable obs, Object arg) {
//instanceof判断其左边对象是否为其右边类的实例
if (obs instanceof WeatherData) {
//强制类型转换
WeatherData weatherData = (WeatherData) obs;
this.temperature = weatherData.getTemperature();
this.humidity = weatherData.getHumidity();
display();
}
}
public void display() {
System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity");
}
}
//WeatherStation测试类
public class Main {
public static void main(String[] args) {
//创建一个WeatherData类的对象,构造方法为默认
WeatherData weatherData = new WeatherData();
//创建一个CurrentConditionsDisplay类的对象
//this是类就是CurrentConditionsDisplay的成员对象,构造方法初始化时将赋给this的Subject属性一个值,值就是传入的weatherData对象,然后注册this到observer观察者队列中
CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
//StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
//ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
//传入更新的数据,setMeasurements会调用measurementsChanged()方法,measurementsChanged()方法会调用setChanged()表示状态已改变,然后调用notifyObservers()方法来通知观察者
weatherData.setMeasurements(80, 65, 30.4f);
weatherData.setMeasurements(82, 70, 29.2f);
weatherData.setMeasurements(78, 90, 29.2f);
}
}
运行截图如下: