观察者模式
定义
首先看下观察者模式的定义
观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新
按我自己的理解翻译就是:
观察者模式就是一个主题和多个依赖者的关系,当主题发生变化时,依赖者得到通知发生对应的变化。用气象站的例子解释:当气象站(主题)检测到天气情况的改变时,会通知不同的显示器(依赖者)显示出天气的情况。同时,由于显示器自身的不同,显现信息也不一样,如气压显示器显示气压,湿度显示器显示湿度。
代码
我们以定义中的气象站为例。
框架
首先给出一个大概的框架:
主要分为了两个接口:主题接口和依赖者(Observer,也称观察者)接口。
主题类中组合了它的依赖者们,所以接口定义了增加依赖者
registerObserver()
、删除依赖者removeObserver()
、以及主题发生变化时通知依赖者变化的方法notifyObservers()
依赖者接口定义了专门给主题类调用的方法
update()
,主题类通过调用依赖类的该方法来改变依赖类。另外还有一个display()
方法来显示出依赖类的改变
详细代码
接下来我们用具体的代码来说明。代码中的主题和依赖者,分别继承和实现了Java.util
中Observable
和Observer
,这是Java中封装好的观察者模式类,有兴趣的可以看下源码。
为了更清晰了解观察者模式,我覆盖了父类的实现。
主题类:
值得注意的地方是,主题通知依赖类,有两种方式:推和拉。
推就是把固定的数据传给依赖类的方法,拉就是把主题传给依赖类,依赖类自己调主题的方法来获取数据。
/**
* 气象站(观察者模式中的主题)
*/
public class WeatherData extends Observable{
/**温度*/
private float temperature;
/**湿度*/
private float humidity;
/**气压*/
private float pressure;
/**该主题的依赖者集合*/
private Vector<Observer> observers = new Vector<>();
/**主题改变标志,用于控制 主题的改变触发依赖类的改变的条件*/
private Boolean isChange = false;
public float getTemperature() {
return temperature;
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
/**
* 增加观察者
*/
@Override
public synchronized void addObserver(Observer observer){
observers.addElement(observer);
}
/**
* 删除观察者
*/
@Override
public synchronized void deleteObserver(Observer observer){
observers.addElement(observer);
}
/**
* 通知观察者(拉)
*/
@Override
public void notifyObservers(){
notifyObservers(null);
}
/**
* 通知观察者(推)
*/
@Override
public synchronized void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
if (!isChange)
return;
arrLocal = observers.toArray();
clearChanged();
}
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
@Override
public void setChanged() {
isChange = true;
}
/**
* 每次数据改动后调用的方法
*/
public void measurementsChanged(){
setChanged();
//将主题传给依赖类,依赖类调用主题的方法自己"拉取"数据
notifyObservers();
//将固定的数据"推"给依赖类,依赖类只能获取指定的数据
/*Map<String, Float> args = new HashMap<>();
args.put("temperature", temperature);
args.put("humidity", humidity);
args.put("pressure", pressure);
notifyObservers(args);*/
}
/**
* 模拟数据改动
*/
public void setMeasurements(float temp, float humidity, float pressure){
this.temperature = temp;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
}
依赖类(观察者类):
由于他们都要显示出气象站的数据,所以定义了一个公共展示接口
public interface Display {
public void display();
}
第一个依赖类,假设只显示气压和温度
/**
* 依赖类,当气象站数据发生改变时,显示数据
*/
public class StatisticsDisplay implements Observer,Display{
/**主题*/
private Observable observable;
private float temperature;
private float pressure;
public StatisticsDisplay(Observable observable) {
this.observable = observable;
observable.addObserver(this);
}
@Override
public void update(Observable o, Object arg) {
//推的方式获取数据
/*if (arg instanceof HashMap) {
HashMap<String, Float> args = (HashMap<String, Float>) arg;
this.temperature = args.get("temperature");
this.pressure = args.get("pressure");
display();
}*/
//拉的方式获取数据
if(o instanceof WeatherData){
WeatherData weatherData = (WeatherData) o;
this.temperature = weatherData.getTemperature();
this.pressure = weatherData.getPressure();
display();
}
}
@Override
public void display() {
System.out.println("TEMP/PRESSURE:"+temperature+" /"+ pressure);
}
}
第二个依赖类,假设只显示温度和湿度:
/**
* 依赖类,当气象站数据发生改变时,显示数据
*/
public class CurrentConditionDisplay implements Observer,Display{
/**主题*/
private Observable observable;
private float temperature;
private float humidity;
public CurrentConditionDisplay(Observable observable) {
this.observable = observable;
observable.addObserver(this);
}
@Override
public void update(Observable o, Object arg) {
//推的方式获取数据
/*if (arg instanceof HashMap) {
HashMap<String, Float> args = (HashMap<String, Float>) arg;
this.temperature = args.get("temperature");
this.humidity = args.get("humidity");
display();
}*/
//拉的方式获取数据
if(o instanceof WeatherData){
WeatherData weatherData = (WeatherData) o;
this.temperature = weatherData.getTemperature();
this.humidity = weatherData.getHumidity();
display();
}
}
@Override
public void display() {
System.out.println("Current condition:"+temperature+" F degree and "+humidity +"% humidity");
}
}
测试:
public class Main {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionDisplay conditionDisplay = new CurrentConditionDisplay(weatherData);
StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
weatherData.setMeasurements(80, 65, 30.4F);
weatherData.setMeasurements(76, 79, 40.4F);
weatherData.setMeasurements(92, 75, 20.4F);
}
}/**output:
TEMP/PRESSURE:80.0 /30.4
Current condition:80.0 F degree and 65.0% humidity
TEMP/PRESSURE:76.0 /40.4
Current condition:76.0 F degree and 79.0% humidity
TEMP/PRESSURE:92.0 /20.4
Current condition:92.0 F degree and 75.0% humidity
**/