Java设计模式之观察者模式的两种实现
观察者模式就是定义对象之间的一对多依赖,这样一来,当一个对象状态发生改变时,它的所有依赖者都会收到通知并自动更新。 这样的好处就是两个或多个对象之间松耦合,它们依然可以交互,但不太清楚彼此的细节。观察者模式提供了一种对象的设计,让主题和观察者之间松耦合。松耦合的设计可以让我们建立有弹性的OO系统,能够应对变化,是因为对象之间的互相依赖降到了最低。
现在我们用一个简单的案例来熟悉观察者模式是怎么实现的。我们设计一个气象站,气象站提供天气数据,天气数据更新后,要实时显示在公告板上。
我们分别用自定义的观察者模式和Java内置的观察者模式两种方式来实现。首先是第一种。
定义主题接口,所有的主题都实现主题接口
package com.example.demo.observer.customize;
/**
* 主题对象
*/
public interface Subject {
/**
* 添加观察者
* @param observer
*/
public void addObserver(Observer observer);
/**
* 删除指定观察者
* @param observer
*/
public void deleteObserver(Observer observer);
/**
* 通知所有观察者
*/
public void notifyObservers();
}
实现我们的主题类,也就是我们的被观察者
package com.example.demo.observer.customize;
import javax.security.auth.Subject;
import java.util.ArrayList;
/**
* 被观察者对象,实现主题接口
*/
public class WeatherData implements Subject {
//温度
private float temperature;
//湿度
private float humidity;
//气压
private float airpressure;
//观察者列表
private ArrayList<Observer> observerArrayList;
public WeatherData() {
this.observerArrayList = new ArrayList<Observer>();
}
/**
* 添加指定观察者对象
* @param observer
*/
@Override
public void addObserver(Observer observer) {
this.observerArrayList.add(observer);
}
/**
* 删除指定观察者对象
* @param observer
*/
@Override
public void deleteObserver(Observer observer) {
int i;
if ((i = observerArrayList.indexOf(observer)) != -1) {
this.observerArrayList.remove(i);
}
}
/**
* 通知观察者
*/
@Override
public void notifyObservers() {
for (Observer observer : this.observerArrayList) {
observer.update(this.temperature, this.humidity, this.airpressure);
}
}
/**
* 被观察者数据发生改变
* @param temperature
* @param humidity
* @param airpressure
*/
public void setMeasurements(float temperature, float humidity, float airpressure) {
this.temperature = temperature;
this.humidity = humidity;
this.airpressure = airpressure;
this.measurementsChanged();
}
/**
* 修改后,通知观察者
*/
public void measurementsChanged() {
this.notifyObservers();
}
}
定义我们的观察者接口,所有的观察者都实现此接口,称为观察者对象
package com.example.demo.observer.customize;
/**
* 观察者接口
*/
public interface Observer {
/**
* 调用观察者者更新接口
* @param temperature
* @param humidity
* @param airpressure
*/
public void update(float temperature, float humidity, float airpressure);
}
定义一个公告板显示接口,观察者要实现此接口来显示在公告板上
package com.example.demo.observer.customize;
public interface DisplayElement {
public void display();
}
定义观察者对象:观察者对象,在初始化构造的时候,注册主题(成为观察主题的对象)
测试我们的观察者模式
package com.example.demo.observer.customize;
import javax.security.auth.Subject;
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private float airpressure;
private Subject subject;
public CurrentConditionsDisplay(Subject subject) {
this.subject = subject;
this.subject.addObserver(this);
}
@Override
public void update(float temperature, float humidity, float airpressure) {
this.temperature = temperature;
this.humidity = humidity;
this.airpressure = airpressure;
this.display();
}
@Override
public void display() {
System.out.println("气温:" + this.temperature + "\t" + "湿度:" + this.humidity + "\t" + "气压:" + this.airpressure);
}
}
package com.example.demo.observer.customize;
public class ForecastDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private float airpressure;
private Subject subject;
public ForecastDisplay(Subject subject) {
this.subject = subject;
this.subject.addObserver(this);
}
@Override
public void update(float temperature, float humidity, float airpressure) {
this.temperature = temperature;
this.humidity = humidity;
this.airpressure = airpressure;
this.display();
}
@Override
public void display() {
System.out.println("气温:" + this.temperature + "\t" + "湿度:" + this.humidity + "\t" + "气压:" + this.airpressure);
}
}
package com.example.demo.observer.customize;
public class StatisticsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private float airpressure;
private Subject subject;
public StatisticsDisplay(Subject subject) {
this.subject = subject;
this.subject.addObserver(this);
}
@Override
public void update(float temperature, float humidity, float airpressure) {
this.temperature = temperature;
this.humidity = humidity;
this.airpressure = airpressure;
this.display();
}
@Override
public void display() {
System.out.println("气温:" + this.temperature + "\t" + "湿度:" + this.humidity + "\t" + "气压:" + this.airpressure);
}
}
第二种实现方式,用Java的内置的API实现观察者模式,主题对象继承Observable类,观察者对象实现Observer接口即可和以上方法类似。修改我们的主题对象。
package com.example.demo.observer.internal;
import java.util.Observable;
public class WeatherData extends Observable {
private float temperature;
private float humidity;
private float airpressure;
public WeatherData() {
}
/**
* 被观察者数据发生改变
* @param temperature
* @param humidity
* @param airpressure
*/
public void setMeasurements(float temperature, float humidity, float airpressure) {
this.temperature = temperature;
this.humidity = humidity;
this.airpressure = airpressure;
this.measurementsChanged();
}
/**
* 修改后,通知观察者
*/
public void measurementsChanged() {
super.setChanged();
super.notifyObservers();
}
public float getTemperature() {
return temperature;
}
public float getHumidity() {
return humidity;
}
public float getAirpressure() {
return airpressure;
}
}
Observable可以主动推(push)数据给观察者对象,也可以让观察者对象拉(pull)数据。
package com.example.demo.observer.internal;
import com.example.demo.observer.customize.DisplayElement;
import java.util.Observable;
import java.util.Observer;
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private float airpressure;
private Observable observable;
public CurrentConditionsDisplay(Observable observable) {
this.observable = observable;
observable.addObserver(this);
}
@Override
public void update(Observable o, Object arg) {
if (o instanceof WeatherData) {
this.temperature = ((WeatherData) o).getTemperature();
this.humidity = ((WeatherData) o).getHumidity();
this.airpressure = ((WeatherData) o).getAirpressure();
this.display();
}
}
@Override
public void display() {
System.out.println("气温:" + this.temperature + "\t" + "湿度:" + this.humidity + "\t" + "气压:" + this.airpressure);
}
}
package com.example.demo.observer.internal;
import com.example.demo.observer.customize.DisplayElement;
import java.util.Observable;
import java.util.Observer;
public class ForecastDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private float airpressure;
private Observable observeable;
public ForecastDisplay(Observable observeable) {
this.observeable = observeable;
this.observeable.addObserver(this);
}
@Override
public void update(Observable o, Object arg) {
if (o instanceof WeatherData) {
this.temperature = ((WeatherData) o).getTemperature();
this.humidity = ((WeatherData) o).getHumidity();
this.airpressure = ((WeatherData) o).getAirpressure();
this.display();
}
}
@Override
public void display() {
System.out.println("气温:" + this.temperature + "\t" + "湿度:" + this.humidity + "\t" + "气压:" + this.airpressure);
}
}
package com.example.demo.observer.internal;
import com.example.demo.observer.customize.DisplayElement;
import java.util.Observable;
import java.util.Observer;
public class StatisticsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
private float airpressure;
private Observable observable;
public StatisticsDisplay(Observable observable) {
this.observable = observable;
this.observable.addObserver(this);
}
@Override
public void update(Observable o, Object arg) {
if (o instanceof WeatherData) {
this.temperature = ((WeatherData) o).getTemperature();
this.humidity = ((WeatherData) o).getHumidity();
this.airpressure = ((WeatherData) o).getAirpressure();
this.display();
}
}
@Override
public void display() {
System.out.println("气温:" + this.temperature + "\t" + "湿度:" + this.humidity + "\t" + "气压:" + this.airpressure);
}
}
测试方法同上。 和自定义观察者模式的区别在于,通知观察者前首先要调用setChanged()方法,修改主题对象的标识,该标识的好处是可以自由的控制主题对象和观察者之间的交互,比如一些微小的数据修改不通知观察者,就可以通过该方法来控制;还有观察者update(Observable o, Object arg)接口的第一个参数Observable o是可以让观察者知道是哪个主题调用它,也可以通过该主题对象,去主题拉取数据。Object arg参数是主题对象推送过来的数据对象,观察者直接可以获取主题对象发送的数据进行处理。
经过测试我们可以观察到,通过Java内置的观察者模式,我们发现Observalbe的notifyObservers()方法通知观察者的时候和我们自定义观察者模式的顺序是不一样的,但如果我们的代码依赖这样的次序,这种实现就是错的。通知次序的改变,很可能会产生错误的结果。
另一方面,Observable是一个类,并不是一个接口,它限制了我们的复用,也违反了我们的OO设计原则(针对接口编程,而非针对实现编程)。如果主题对象要想继承其他超类,就会陷入两难的境地,毕竟Java不支持多继承,限制了Observable的复用潜力。再者,因为没有Observable接口,所以也无法建立自己的实现,和Java内置Observer API搭配使用。而且也违反了我们的设计原则:多用组合,少用继承。
所以不管用哪种方法都可以实现观察者模式,前提是根据我们的业务场景来。