观察者模式:定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会受到通知并自动更新(采用推的模式,当然也可以采用拉的模式)
主题(Subject) + 观察者(Observer) = 观察者模式
举个栗子:
场景:有一个天气气象站,根据采集到的温度、湿度、气压,会有三个不同的天气数据的布告板:1、“目前状况”布告;2、“气象统计”布告;3、“天气预报”布告。一旦WeatherData有新的测量,这些布告必须马上更新。
先看一个不好的代码示范:
WeatherData.java 类如下:
public class WeatherData {
//实例变量声明
public void measurementsChanged(){
float temp = getTemperature();
float humidity = getHumidity();
float pressure = getPressure();
currentConditionsDisplay.update(tem, humidity, pressure);
statisticsDisplay.update(temp, humidity, pressure);
forecastDisplay.update(temp, humidity, pressure);
//这里是其他WeatherData方法
}
}
缺点:上面是针对接口实现进行编程,而非针对接口;对于每个新的布告板,我们都得要修改代码;无法在运行时动态地增加(或删除)布告栏;布告栏没有一个共同的接口;未封装改变的部分;侵犯了WeatherData类的封装。
易知,三个布告栏的数据与天气源的三个数据温度、湿度、气压相关,也就是天气源的三个数据变化三个布告栏的数据也要跟着变化,这与观察者模式相吻合。
采用观察者模式(推荐):
主题被观察者Subject.java 类:
public interface Subject {
public void registerObserver(Observer observer); //注册观察者
public void removeObserver(Observer observer); //移除观察者
public void notifyObservers(); //通知观察者
}
观察者接口Observer.java 类:
public interface Observer {
public void update(float temp, float humidity, float pressure);
}
布告栏展示接口DisplayElement.java 类:
public interface DisplayElement {
public void display();
}
订阅者实现类WeatherData.java 类:
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 observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
int i = observers.indexOf(observer);
if(i > 0){
observers.remove(observer);
}
}
@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();
}
}
几个布告栏的实现类:
“目前状况”布告栏CurrentConditionsDisplay.java 类:
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private float temperature;
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:" + temperature + " F degrees and " + humidity + "% humidity");
}
@Override
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
display();
}
}
“气象统计”布告栏 StatisticsDisplay.java 类:
public class StatisticsDisplay implements Observer, DisplayElement {
private Subject weatherData;
private float averageTemp = 0f;
private float lowestTemp = 0f;
private float highestTemp = 0f;
private ArrayList<Float> tempList = new ArrayList<>();
public StatisticsDisplay(Subject weatherData){
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
@Override
public void update(float temp, float humidity, float pressure) {
tempList.add(temp);
this.averageTemp = this.getAverageTemp(tempList);
this.highestTemp = Collections.max(tempList);
this.lowestTemp = Collections.min(tempList);
display();
}
@Override
public void display() {
System.out.println("Statistics conditions:average temperature:" + averageTemp + " F degrees and lowest temperature:" + lowestTemp + " and highest temperature:"
+ highestTemp + " F degree");
}
private float getAverageTemp(List<Float> tempList){
int size = tempList.size();
if(size <= 0){
return 0f;
}
float sum = 0f;
for(int i=0;i<size;i++){
sum += tempList.get(i);
}
return sum/size;
}
}
-----------------------------------我是分割线-------------------------------------------
实际调用WeatherStation.java 类:
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData);
StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
weatherData.setMeasurements(89, 20,33);
weatherData.setMeasurements(100, 2,3);
}
}
输出结果如下:
Current conditions:89.0 F degrees and 20.0%humidity
Statistics conditions:averagetemperature:89.0 F degrees and lowest temperature:89.0 and highesttemperature:89.0 F degree
Current conditions:100.0 F degrees and 2.0%humidity
Statistics conditions:averagetemperature:94.5 F degrees and lowest temperature:89.0 and highesttemperature:100.0 F degree
分析上述代码:
主题Subject.java 也即我们的订阅者接口,需要声明注册(添加)、移除、通知观察者的行为。
观察者Observer.java 都需要一个更新方法update()。
另外对于每个具体的实现者都会需要一个展示方法display(),因此增加一个DisplayElement接口。
其次,对于主题Subject的实现类WeatherData.java 中需要对温度、湿度、气压,以及一个观察者List对所有观察者进行注册和移除,当然还包括对所有观察者进行通知操作。
最后,对于所有实现Observer.java的观察者,都需要对统一的订阅者进行注册操作。
上述的代码,实际上采用的是推的形式进行对订阅者进行消息更新。
另外,在Java API有内置的观察者模式。Java.util包(package)内包含最基本的Observer接口与Observable类,这和我们上面的Subject接口与Observer接口很类似。Java.util包提供的Observer接口与Observable类使用上更方便,因为有许多功能已经事先准备好了。注意:你甚至可以使用推(push)或者拉(pull)的方式传送数据。
WeatherData.java 类:public class WeatherData extends Observable { //Observable是java.util内置的类
private float temperature;
private float humidity;
private float pressure;
public WeatherData(){
}
public void measurementChanged(){
setChanged(); //这个设置决定了是推,还是拉,来进行消息通知
notifyObservers();
}
public void setMeasurements(float temperature, float humidity, float pressure){
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementChanged();
}
public float getTemperature(){
return temperature;
}
public float getHumidity(){
return humidity;
}
public float getPressure(){
return pressure;
}
}
注意:setChanged()方法是Observable内置的设置是否当源数据一改变就通知订阅者进行更新,调用时即为推的模式,注释掉的话则为拉的模式。
实现类:
CurrentConditionsDisplay.java 类:
public class CurrentConditionsDisplay implements Observer, DisplayElement {
Observable observable;
private float temperature;
private float humidity;
public CurrentConditionsDisplay(Observable observable){
this.observable = observable;
observable.addObserver(this);
}
@Override
public void display() {
System.out.println("Current conditions: " + temperature + " F degrees and " + humidity + " % humidity");
}
@Override
public void update(Observable obs, Object arg) {
if(obs instanceof WeatherData){
WeatherData weatherData = (WeatherData)obs;
this.temperature = weatherData.getTemperature();
this.humidity = weatherData.getHumidity();
display();
}
}
}
具体使用时的类如下:
WeatherStation.java 类:public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData);
weatherData.setMeasurements(12, 33, 12);
currentConditionsDisplay.update(weatherData, null);
}
}
需要注意:实际在用观察者模式,且用到java.util内置的观察者模式,需要去查看源码是如何实现的。