1.认识观察者模式
通过报纸的订阅来认识观察者模式:
①报社的业务就是出版报纸
②向某家报社订阅报纸,只要他们有新报纸出版,就会给你送来。只要你是他们的订户,你就会一直收到新报纸
③当你不想再看报纸的时候,取消订阅,他们就不会再送新报纸来
④只要报社还在运营,就会一直有人(或单位)向他们订阅报纸或取消订阅报纸。
如果你了解报纸的订阅是怎么回事,其实就知道观察者模式是怎么回事,只是名称不太一样:出版者改称为“主题”(Subject),订阅者改称为“观察者”(Observer)。
2.定义
观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
3.松耦合
观察者模式提供了一种对象设计,让主题和观察者之间松耦合。当两个对象之间松耦合,他们依然可以交互,但是不太清楚彼此的细节。
为什么说观察者模式让主题和观察者之间松耦合?
①关于观察者的一切,主题只知道观察者实现了某个接口(也就是Observer接口)。主题不需要知道观察者的具体类是谁、做了些什么或其他任何细节。
②在运行时我们可以用新的观察者取代现有的观察者,主题不会受到任何影响。同样的,也可以在任何时候删除某些观察者。改变主题或观察者其中一方,并不会影响另一方。
③如果我们在其他地方需要使用主题或观察者,可以轻易地复用,因为二者并非紧耦合。
*设计原则:为了交互对应之间的松耦合设计而努力。
4.例子
背景:你的团队负责建立W公司的下一代气象站,该公司希望能建立一个应用有三种布告板,分别显示目前的状况、气象统计及简单的预报。当获取到最新的测量数据时,三种布告板必须实时更新。(下面的代码展示了主题和其中一个目前状况的布告板)
主题接口:
public interface Subject {
public void registerObserver(Observer o);//注册
public void removeObserver(Observer o);//移除
public void notifyObservers();//通知
}
主题实例:
public class WeatherData implements Subject {//WeatherData现在实现了Subject接口
private ArrayList<Observer> observers;//我们加上一个ArrayList来记录观察者,此ArrayList是在构造器中建立的
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
observers = new ArrayList<Observer>();
}
public void registerObserver(Observer o) {//当注册观察者时,我们只要把它加到ArrayList的后面即可
observers.add(o);
}
public void removeObserver(Observer o) {//同样地,当观察者想取消注册,我们把它从ArrayList中删除即可
int i = observers.indexOf(o);
if (i >= 0) {
observers.remove(i);
}
}
public void notifyObservers() {//有趣的地方来了!在这里,我们把状态告诉每一个观察者。因为观察者都实现了update(),所以我们知道如何通知它们
for (Observer observer : observers) {
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 float getTemperature() {
return temperature;
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
}
观察者接口:
public interface Observer {
public void update(float temp, float humidity, float pressure);
}
观察者实例:
/**
* 布告板
* 此布告板实现了Observer接口,所以可以从WeatherData对象中获得改变
* 它也实现了DisploayElement接口,因为我们的API规定所有的布告板都必须实现此接口
* Created by renyi on 2019/2/5 7:37 PM
*/
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private Subject weatherData;
/**
* 构造器需要weatherData对象(也就是主题)作为注册之用
*
* @param weatherData
*/
public CurrentConditionsDisplay(Subject weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
/**
* 当update()被调用时,我们把温度和湿度保存起来,然后调用display()
*
* @param temperature
* @param humidity
* @param pressure
*/
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
display();
}
/**
* display()方法就只是把最近的温度和湿度显示出来
*/
public void display() {
System.out.println("Current conditions: " + temperature
+ "F degrees and " + humidity + "% humidity");
}
}
使用java内置的观察者模式:
java.util包内包含最基本的Observer接口与Observable类,这和我们的Subject接口与Obserer接口很相似。下面我们用java.util中的内置观察者模式将上面的例子重写。
主题(可观察者):
/**
* 利用内置的支持重做气象站
* 我们不再需要追踪观察者了,也不需要管理注册与删除(让超类代劳即可)。所以我们把注册、添加、通知的相关代码删除即可
* Created by renyi on 2019/2/5 8:51 PM
*/
public class WeatherData extends Observable {
private float temperature;
private float humidity;
private float pressure;
public WeatherData() { }//我们的构造器不再需要为了记住观察者们而建立数据结构了
public void measurementsChanged() {//注意:我们没有调用notifyObservers()传送数据对象,这表示我们采用的做法是拉
setChanged();//在调用notifyObservers()之前,要先调用setChanged()来指示状态已经改变
notifyObservers();
}
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
//这些并不是新方法,只是因为我们要使用"拉"的做法,所以才提醒你有这些方法。观察者会利用这些方法取得WeatherData对象的状态
public float getTemperature() {
return temperature;
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
}
观察者:
public class CurrentConditionsDisplay implements Observer, DisplayElement {
Observable observable;
private float temperature;
private float humidity;
public CurrentConditionsDisplay(Observable observable) {//现在构造器需要一Observable当参数,并将CurrentConditionDisplay对应登记为观察者
this.observable = observable;
observable.addObserver(this);
}
public void update(Observable obs, Object arg) {//改变update()方法,增加Observable和数据对象作为参数
if (obs instanceof WeatherData) {//在update()中,先确定可观察者属于WeatherData类型,然后利用getter方法获取温度和湿度测量值,最后调用display()
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");
}
}
5.优点
1.观察者模式降低了主题和观察者之间的耦合。
2.观察者模式支持广播通讯。被观察者会向所有的登记过的观察者发出通知。
6.缺点
1.如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
2.如果在被观察者之间有循环依赖的话,被观察者会触发它们之间进行循环调用,导致系统崩溃。在使用观察者模式是要特别注意这一点。
3.虽然观察者模式可以随时使观察者知道所观察的对象发生了变化,但是观察者模式没有相应的机制使观察者知道所观察的对象是怎么发生变化的。