基本介绍

观察者模式(Observer Design Pattern)也被称为发布订阅模式(Publish-Subscribe Design Pattern)

意图:当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并被自动更新。

观察者模式属于行为型模式, 大多应用于一些事件驱动模型(Spring涉及)或者游戏开发领域。

 

假设有一家气象局,姑且就叫神盾气象局吧,该气象局委托我们构建一套系统,这个系统有两个公告牌,需要我们显示当前的实时天气和未来的天气预报。 当神盾气象局发布新的天气数据(WeatherData)后,两个公告牌上显示的天气数据务必实时更新。神盾气象局同时要求我们保证程序拥有足够的可扩展性,因为以后随时要新增其他的公告牌(如紧急公告等)。

他们最初始的设计如下:

public class WeatherData {

    //实例变量声明
    ...
    public void measurementsChanged() {

        float temperature = getTemperature();
        float humidity = getHumidity();
        float pressure = getPressure();
        List<Float> forecastTemperatures = getForecastTemperatures();

        //更新公告牌
        currentConditionsDisplay.update(temperature, humidity, pressure);
        forecastDisplay.update(forecastTemperatures);
    }
    ...
}

 

然鹅每当我需要删除或者新增公告牌时,我就必须得修改 核心逻辑代码,如此看来扩展性极差,违背了开闭原则(对扩展开放,对修改关闭)

从上述方案,我们已经大致了解该方案有很多问题

  • 扩展性较差
  • 代码功能耦合严重
  • 核心功能代码改来改去容易出问题

 

观察者模式

观察者模式通常情况所解决的需求场景:A对象(观察者)被 B对象(被观察者)的某种变化高度敏感,需要在B变化的那一刻A及时得到反馈。

举个例子:小明过马路,小明需要在红绿灯由红灯变成绿灯后到达马路另一侧,这个场景中,小明是观察者,红绿灯是被观察者,当红绿灯发生颜色改变时,小明需要得到反馈。

但是程序中的观察和现实中所说的【观察】有些许差异:

  • 观察者不需要时刻盯着被观察者(小明不需要每一秒盯着红绿灯)
  • 采用注册或者订阅(Subscribe)的方式告诉被观察者(红绿灯变色后会广播,小明可以听到)

采取这样被动的观察方式,省去了反复检索状态的资源消耗,也能得到最快的反馈速度,其实就是由拉变成了推

 

观察者模式通常基于 Subject(主题)和 Observer(观察者)而设计,类图如下

观察者模式-LMLPHP

 

既然我们说了神盾气象局最初的设计 多么的糟糕,那么我们现在就用观察者模式来重构它吧!

首先我们基于上述的通用类图,重新构建神盾气象局的结构类图以及编码实现

观察者模式-LMLPHP

 

主题接口

/**
 * 主题
 */
public interface Subject {

    void registerObserver(Observer observer);

    void removeObserver(Observer observer);

    void notifyObserver();

}

 

观察者接口

/**
 * 观察者
 */
public interface Observer {

    //反向更新
    void update(WeatherDetail weatherDetail);

}

 

公告牌接口

public interface DisplayElement {

    void display();

}

 

气象数据Entity

@Data
@AllArgsConstructor
public class WeatherDetail {

    private double temperature; //当前温度
    private double humidity; //当前湿度
    private double pressure; //当前气压
    private List<WeatherDetail> forecastDetails;//未来几天的气象数据详情

}

 

气象数据(被观察者)- 核心

@Data
public class WeatherData implements Subject {

    private List<Observer> observers;
    private WeatherDetail weatherDetail;

    public WeatherData() {
        observers = new ArrayList<>();
    }

    @Override
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
        if (!observers.isEmpty()) {
            observers.remove(observer);
        }
    }

    @Override
    public void notifyObserver() {
        for (Observer observer : observers) {
            observer.update(weatherDetail);
        }
    }

    public void setMeasurements(WeatherDetail weatherDetail){
        this.weatherDetail = weatherDetail;
        notifyObserver();
    }

}

 

显示当前天气的公告牌(CurrentConditionDisplay)

/**
 * 当前展板
 */
public class CurrentConditionDisplay implements Observer, DisplayElement {

    private double temperature; //当前温度
    private double humidity; //当前湿度
    private double pressure; //当前气压

    public CurrentConditionDisplay(Subject weatherData) {
        //天气数据
        weatherData.registerObserver(this);
    }

    @Override
    public void display() {
        System.out.println("current-[temperature:" + temperature + ",humidity:" + humidity + ",pressure:" + pressure + "]");
    }

    @Override
    public void update(WeatherDetail weatherDetail) {
        this.temperature = weatherDetail.getTemperature();
        this.humidity = weatherDetail.getHumidity();
        this.pressure = weatherDetail.getPressure();
    }
}

 

显示未来几天天气的公告牌(ForecastConditionDisplay)

ppublic class ForecastConditionDisplay implements Observer, DisplayElement {

    private List<WeatherDetail> forecastDetails;//未来几天的气象数据详情

    public ForecastConditionDisplay(Subject weatherData) {
        weatherData.registerObserver(this);
    }

    @Override
    public void display() {
        if (forecastDetails != null) {
            for (WeatherDetail weatherDetail : forecastDetails) {
                System.out.println("forecast-[temperature:" + weatherDetail.getTemperature()
                        + ",humidity" + weatherDetail.getHumidity() + ",pressure" + weatherDetail.getPressure() + "]");
            }
        }
    }

    @Override
    public void update(WeatherDetail weatherDetail) {
        forecastDetails = weatherDetail.getForecastDetails();
    }
}

 

到这里,我们整个神盾气象局的WeatherData应用就改造完成了。

两个公告牌 CurrentConditionsDisplayForecastConditionDisplay 实现了 ObserverDisplayElement 接口。在他们的构造方法中会调用 WeatherDataregisterObserver 方法将自己注册成观察者,这样被观察者 WeatherData 就会持有观察者的应用,并将它们保存到一个集合中。当被观察者 WeatherData 状态发生变化时就会遍历这个集合,循环调用观察者更新公告牌数据的方法。后面如果我们需要增加或者删除公告牌,就只需要新增或者删除实现了 ObserverDisplayElement 接口的公告牌就好了。

 

好,我们接下来测试一下利用观察者模式改进后的程序.....

public class Client {
	public static void main(String[] args) {
		List<WeatherDetail> forecastDetail = new ArrayList<>();
		forecastDetail.add(new WeatherDetail(23.0, 0.9, 1.1, null));
		forecastDetail.add(new WeatherDetail(17.0, 0.5, 1.3, null));
		WeatherDetail weatherDetail = new WeatherDetail(22.0, 0.8, 1.2, forecastDetail);
		WeatherData weatherData = new WeatherData();
		DisplayElement current = new CurrentConditionDisplay(weatherData);
		DisplayElement forecast = new ForecastConditionDisplay(weatherData);
		weatherData.setMeasurements(weatherDetail);

		current.display();
		forecast.display();
	}
}

输出结果
观察者模式-LMLPHP
 

观察者模式(JDK版)

我们还是用神盾气象局的例子来实现JDK版本的观察者模式,这样便于比较两者之间的差别
 
公告牌接口

public interface DisplayElement {

    void display();

}

 
气象数据Entity

@Data
@AllArgsConstructor
public class WeatherDetail {

    private double temperature; //当前温度
    private double humidity; //当前湿度
    private double pressure; //当前气压
    private List<WeatherDetail> forecastDetails;//未来几天的气象数据详情

}

 
气象数据(被观察者)- 核心

/**
 * 天气数据
 */
@Data
public class WeatherData extends Observable {

    private WeatherDetail weatherDetail;

    public WeatherData() {}


    public void setMeasurements(WeatherDetail weatherDetail){
        this.weatherDetail = weatherDetail;
        this.setChanged();
        notifyObservers(weatherDetail);
    }

}

 
显示当前天气的公告牌(CurrentConditionDisplay)

/**
 * 当前展板
 */
public class CurrentConditionDisplay implements Observer, DisplayElement {

    private double temperature; //当前温度
    private double humidity; //当前湿度
    private double pressure; //当前气压

    public CurrentConditionDisplay(Observable weatherData) {
        //天气数据
        weatherData.addObserver(this);
    }

    @Override
    public void display() {
        System.out.println("current-[temperature:" + temperature + ",humidity:" + humidity + ",pressure:" + pressure + "]");
    }


    @Override
    public void update(Observable observable, Object arg) {
        WeatherDetail weatherDetail = (WeatherDetail) arg;
        this.temperature = weatherDetail.getTemperature();
        this.humidity = weatherDetail.getHumidity();
        this.pressure = weatherDetail.getPressure();
    }
}

 
显示未来几天天气的公告牌(ForecastConditionDisplay)

public class ForecastConditionDisplay implements Observer, DisplayElement {

    private List<WeatherDetail> forecastDetails;//未来几天的气象数据详情

    public ForecastConditionDisplay(Observable weatherData) {
        //天气数据
        weatherData.addObserver(this);
    }

    @Override
    public void display() {
        if (forecastDetails != null) {
            for (WeatherDetail weatherDetail : forecastDetails) {
                System.out.println("forecast-[temperature:" + weatherDetail.getTemperature()
                        + ",humidity:" + weatherDetail.getHumidity() + ",pressure:" + weatherDetail.getPressure() + "]");
            }
        }
    }

    @Override
    public void update(Observable observable, Object arg) {
        WeatherDetail weatherDetail = (WeatherDetail) arg;
        this.forecastDetails = weatherDetail.getForecastDetails();
    }

}

 
到这里,我们使用JDK自带的API实现了观察者模式,下面我们开始测试

public class Client {
    public static void main(String[] args) {
        List<WeatherDetail> forecastDetail = new ArrayList<>();
        forecastDetail.add(new WeatherDetail(23.0, 0.9, 1.1,null));
        forecastDetail.add(new WeatherDetail(17.0, 0.5, 1.3,null));
        WeatherDetail weatherDetail = new WeatherDetail(22.0, 0.8, 1.2, forecastDetail);
        WeatherData weatherData = new WeatherData();
        DisplayElement current = new CurrentConditionDisplay(weatherData);
        DisplayElement forecast = new ForecastConditionDisplay(weatherData);
        weatherData.setMeasurements(weatherDetail);

        current.display();
        forecast.display();
    }
}

输出结果
观察者模式-LMLPHP
 
总结:使用JDK自带的API去实现观察者模式固然方便,但是由于需要继承 Observable 接口,会对被观察者类造成限制(单继承的局限性),其次 Observable 的代码 从属JDK1.0,底层还是用的相关的Vector去做安全的集合容器,个人感觉还是有点过时了,个人还是倾向于自实现观察者模式。

07-21 19:36