先看一个需求:我们要做一个气象监控软件,weatherData对象负责追踪目前的天气状况(温度、湿度、气压),另外需要分别在三个显示屏上显示目前的状况、气象统计和简单的预报,并且是需要实时更新的。
这是典型的一对多问题,而观察者模式就是定义了一系列对象之间的一对多关系,但一个对象改变状态时,其他依赖着都会收到通知,并自动更新。这非常符合我们的需求,三个显示屏甚至更多的显示屏依赖weatherData对象,当weatherData监控到的数据改变是,我们的显示屏也是会实时改变的。
在实现观察者模式之前请紧记我们的原则:针对接口编程,而不是针对实现。观察者模式的实现方式 有很多种,但下面这种是最常见的:
先定义一个主题接口:
public interface Subject {
public void registerObserver(Observer o);//注册观察者
public void removeObserver(Observer o);//移除观察者
public void notifyObserver();//通知观察者
}
每个主题有多个观察者,所以再定义一个观察者接口:
public interface Observer {
public void update();
}
接下来就是具体主题对象和具体观察者的创建了,每个具体主题对象必须实现主题接口,每个具体观察者实现观察者接口,并且需要注册具体主题,你不注册主题又怎会给你推送新的通知呢?
显然,观察者模式很大程度地实现了对象之间的松耦合,主题只是知道观察者实现了某个接口(也就是observer接口),而观察者具体是哪个类,做了什么以及其他的一些细节,主题都不知道。而且,在任何时候我们都可以增加新的观察者,或是取代现有的观察者,哪怕是删除现有的观察者,主题都不会受到任何影响。主题做的只是发送通知给所有实现了观察者接口的对象。
松耦合的设计让对象之间的相互依赖到了最低,也就能让我们的OO系统更加弹性,可以应对更多的变化。
回到刚才的项目上,显然,我们的项目和观察者模式是相吻合的,那么我们该如何实现呢?
先定义主题接口:
public interface Subject {
public void registerObserver(Observer o);//注册观察者
public void removeObserver(Observer o);//移除观察者
public void notifyObserver();//通知观察者
}
观察者接口:
public interface Observer {
public void update(float temp, float humidity, float pressure);
}
我们还需要一个展示的接口:
public interface DisplayElement {
public void display();
}
好的,重点来了,看看我们的具体主题对象weatherData的实现:
public class WeatherData implements Subject {
private ArrayList<Observer> 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(o);
}
}
@Override
public void notifyObservers() {
for(int i = 0; i < observers.size(); i++){
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();
}
//weatherData的其他方法
}
好吧,接下来就是我们的具体观察者对象了:
//只关注湿度和温度
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private float temperatur;
private float humidity;
private Subject weatherData;
public CurrentConditionsDisplay(Subject weatherData){
this.weatherData = weatherData;//保存引用是为了以后可以随时取消关注
weatherData.registerObserver(this);
}
@Override
public void display() {
System.out.println("Current conditions: " + temperatur + "F degress and " + humidity + "% humidity");
}
//所以只传温度和湿度的值
@Override
public void update(float temp, float humidity, float pressure) {
this.temperatur = temp;
this.humidity = humidity;
display();
}
}
如果你还需要建立其他观察者对象也是类似的。
来个测试类:
public class Main {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentCondition = new CurrentConditionsDisplay(weatherData);
weatherData.setMeasurements(80, 48, 99);
}
}
这就是一个完整的观察者模式的样式,但是大家是否有注意到问题:主题对象总是强迫地把它的信息传送给所有的观察者,而有时候,有些观察者并不需要所有的数据,我们是不是可以主动地去拉取我们需要的数据呢?
答案是肯定的,但是这种“拉”的方式也有它的不足:每次需要数据时才去找主题对象,如果更新比较频繁的话,这种方式的缺点也是很明显的。
虽然我们已经从无到有实现了观察者模式,但是java API中已有内置实现的方式:具体主题对象继承Observable和具体观察者对象实现observer接口。我们可以修改一下之前的例子来对比一下
具体主题对象,注意Observable 要importjava .util包下的:
public class WeatherData2 extends Observable {
private ArrayList<Observer> observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherData2(){}//不需要为了记住观察者对象而建立数据结构
//当气象站得到更新数据后,通知观察者
public void measurementsChanged(){
setChanged();
notifyObservers();//调用notifyObservers之前需要先调用setChanged表示状态已改变
}
public void setMeasurements(float temperature, float humidity, float pressure){
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
public float getTemperature() {
return temperature;
}
//get方法主要是为了实现“拉”的方式
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
//weatherData的其他方法
}
具体观察者
//只关注湿度和温度
public class CurrentConditionsDisplay2 implements Observer, DisplayElement {
private float temperatur;
private float humidity;
private Observable observable;
public CurrentConditionsDisplay2(Observable observable) {
this.observable = observable;// 保存引用是为了以后可以随时取消关注
observable.addObserver(this);
}
@Override
public void display() {
System.out.println("Current conditions: " + temperatur + "F degress and " + humidity + "% humidity");
}
// 所以只传温度和湿度的值
public void update(Observable obs, Object arg) {
if (obs instanceof WeatherData2) {
WeatherData2 weatherData = (WeatherData2) obs;
this.temperatur = weatherData.getTemperature();
this.humidity = weatherData.getHumidity();
display();
}
}
}
是的,我想熟悉OO设计原则的你应该发现问题了:其一,Observable是一个类,如果我们某个类想同时具有Observable和另一个超类的行为,就会陷入两难,毕竟java不支持多重继承。其二,setChanged()方法被定义为protected,这样,除非你继承Observable,否则你无法创建Observable实例并且组合到你自己的对象来。
除非你能够扩展Observable,那么Observable可能符合你的需求,否则你还是应该要像开头那样自己实现一套完整的观察者模式。