观察者模式:定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
举例,如报纸和杂志的订阅:
1.报社的业务就是出版报纸
2.向某家报社订阅报纸,只要他们有新报纸出版就会给你送来。只要你是他们的订户,你就会一直收到新报纸
3.当你不想再看报纸的时候,取消订阅,他们就不会再送新报纸来
4.只要报社还在运营,就会一直有人(或单位)向他们订阅报纸或取消订阅报纸
上面的出版者就是“主题”(Subject),订阅者就是"观察者"(Observer)
让我们来看得更仔细一点:
主题对象管理某些数据,当主题内的数据改变,就会通知观察者。
观察者都是已经注册到主题的,以便在主题数据改变时能够收到更新通知。当然观察者也可以取消注册而失去作为观察者的资格也就不会再收到主题的通知了。
主题就是"一",观察者们就是"多",这样构成一对多的关系,且观察者们的更新依赖于主题的通知。
下面使用该模式来解决(模拟)气象站问题:
Weather-O-Rama气象站计划建立下一代的Internet气象观察站,该气象站必须建立在WeatherData对象的基础上,WeatherData对象提供天气数据,有三种布告板,分别显示目前的状况、气象统计及简单的预报。并且以后可以方便地增加布告板进行扩展。
使用观察者模式进行设计,WeatherData对象即观察者模式中的主题对象,三个布告板即观察者。
实现如下(java描述):
//主题
public interface Subject {
public void registerObserver(Observer o); //注册观察者
public void removeObserver(Observer o); //取消注册
public void notifyObservers(); //通知观察者更新
}
//观察者
public interface Observer {
public void update(float temp,float humidity,float pressure); //temp,humidity,pressure分别代表气象的温度、湿度、气压数值
}
//具体主题
public class WeatherData implements Subject {
private ArrayList observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherData(){
observers = new ArrayList();
}
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
@Override
public void removeObserver(Observer o) {
int i = observers.indexOf(o);
if(i >= 0){
observers.remove(i);
}
}
@Override
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();
}
}
//具体观察者
public interface DisplayElement { //该接口是辅助接口,只用来显示
public void display();
}
public class CurrentConditionsDisplay implements Observer, DisplayElement { //目前的状况 布告板,气象统计及简单的预报这里不再给出
private float temperature;
private float humidity;
private Subject weatherData;
public CurrentConditionsDisplay(Subject weatherData){
this.weatherData = weatherData; //保留此变量,方便以后取消注册
this.weatherData.registerObserver(this); //注册
}
@Override
public void display() {
System.out.println("Current conditions: "+this.temperature+"F degrees and "+this.humidity+"% humidity");
}
@Override
public void update(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
display(); //update()并不是最适合调用display()的地方,但在该简单例子中是很合理的
}
}
//运行(气象站)测试
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
weatherData.setMeasurements(80, 65, 30.4f);
weatherData.setMeasurements(82, 70, 29.2f);
weatherData.setMeasurements(78, 90, 29.2f);
}
}
//运行结果
Current conditions: 80.0F degrees and 65.0% humidity
Current conditions: 82.0F degrees and 70.0% humidity
Current conditions: 78.0F degrees and 90.0% humidity
呵呵,其实上面的设计还是有缺点的,如观察者接口中public void update(float temp,float humidity,float pressure); 如果对应要更新的状态还需要增加一个或多个时,这里不得不修改该方法,增加对应的传参。当然,如果这个数据参数是固定的,那么这个做法也是最直接的。下面是针对不固定的更新状态的观察者方案:
public interface Observer {
public void update(Subject o);
}
这个方案可以让具体的观察者根据自己的需要去获取"主题"(Subject)数据,但是要求"主题"提供一些公开的getter方法。