设计模式-观察者模式(Subject-Observer)

背景:建立一个应用,利用后台的核心对象WeatherData获取气象站的数据,并更新应用的的三个布告板:目前状况、气象统计和天气预报。一旦后台的WeatherData数据有更新,布告板应该马上更新,或者知道数据更新了,从而执行刷新操作。系统应该可扩展,让用户可以随心所欲的添加删除布告板。
设计模式-观察者模式(Subject-Observer)_第1张图片

撸起袖子就是干之快速实现:

/**
 * 简单的应用后台
 */
public class WeatherData {
    /**
     * 目前天气布告板
     */
    private CurrentConditionsDisplay currentConditionsDisplay;
    /**
     * 天气统计布告板
     */
    private StatisticsDisplay statisticsDisplay;
    /**
     * 天气预告布告板
     */
    private ForecastDisplay forecastDisplay;

    /**
     * (假设定时任务每分钟都会调取该方法)获取气象数据,并判断是否需要更新布告板
     */
    public void handleData() {
        float temp = getTemperature();
        float humidity = getHumidity();
        float pressure = getPressure();
        if (!"温度、湿度、气压".equals("不等于当前的布告板数据,则更新布告板")) {
            measurementsChanged(temp, humidity, pressure);
        }
    }

    public void measurementsChanged(float temp, float humidity, float pressure) {
        //调用目前天气布告板对象的更新方法布告板
        currentConditionsDisplay.update(temp, humidity, pressure);
        //调用天气统计布告板对象的更新方法布告板
        statisticsDisplay.update(temp, humidity, pressure);
        //调用天气预告布告板对象的更新方法布告板
        forecastDisplay.update(temp, humidity, pressure);
    }

    public float getTemperature() {
        System.out.println("获取温度数据");
        return new Random().nextFloat();
    }

    public float getHumidity() {
        System.out.println("获取湿度数据");
        return new Random().nextFloat();
    }

    public float getPressure() {
        System.out.println("获取气压数据");
        return new Random().nextFloat();
    }
}

上述的快速实现版应用分析

1.如果上述的CurrentConditionsDisplay等布告板对象为具体类,而非接口,那么违背了针对接口编程,而非具体实现编程的原
则,会导致每次修改布告板都需要重新打开具体类进行修改代码
2.对每个新的布告板都需要修改核心代码(修改WeatherData的代码),也无法满足定制需求(动态的增加或删除布告板)
3.尚未封装改变的部分(update方法就是改变的部分,根据需求的不一样,虽然获取一样的数据,处理方式却不一样,所以可定制
成一个接口)

此题主要是讲观察者模式,那么解决此问题的办法自然是观察者模式,先看看什么是观察者模式。

观察者模式

在这里WeatherData,从探测器处获取数据,从而持有数据,然后布告板有N个,当数据变化时每个布告板都从WeatherData处获取一样的数据来更新各自的布告板,那么可以理解:N个布告板从WeatherData处订阅数据,当WeatherData的数据出现变化时,便广播给布告板,它们一对多的关系,此处将WeatherData称之为‘主题’,布告板称为‘观察者’,‘主题’+‘观察者’=观察者模式
设计模式-观察者模式(Subject-Observer)_第2张图片
实现观察者最常见的是包含Subject与Observer接口的类设计,关系看以下类图
设计模式-观察者模式(Subject-Observer)_第3张图片
Subject接口定义主题的订阅、取消订阅等方法。Subject的实现类即真正的主题(仅一个实现)、数据的持有者,使用ArrayList集合来记录实现了Observer接口的订阅者(多个实现)。观察者持有Subject的引用,方便注册订阅、取消订阅。
至此,我们便可以简单搭建起观察者模式的气象应用了,代码如下:

//主题接口
 public interface Subject {
 	//消费者注册接口,进行数据订阅
    public void registerObserver(Observer observer);
    //消费者移除接口,取消数据订阅
    public void removeObserver(Observer observer);
    //数据广播接口,当数据出现变更时,将更新后的数据传输给消费者
    public void notifyObservers();
}

//天气主题--实现主题接口
public class WeatherData implements Subject {
	//成员变量集合容器,用来承装消费者接口的引用,消费者进行数据订阅就添加到容器中去,取消订阅就从容器中移除
    private ArrayList observers;
    private  double temp;
    private  double humidity;
    private  double pressure;

    public WeatherData() {
    //构造器中实例化容器
        this.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(i);
        }
    }

    @Override
    public void notifyObservers() {
    	//遍历集合容器,然后将数据传递各自订阅者的处理方法进行数据处理
        observers.forEach(e->e.update(temp,humidity,pressure));
    }
    /**
    *设置最新获取到的数据
    **/
    public void setMeasurements(double temp,double humidity,double pressure){
        this.temp=temp;
        this.humidity=humidity;
        this.pressure=pressure;
        //同时将最新获取到的数据广播给每一个订阅了数据的消费者
        measurementsChanged();
    }
    public  void measurementsChanged(){
    	//调用广播方法
        notifyObservers();
    }
    //其他方法
}

===================================== 观察者=====================================

//观察者接口
public interface Observer {
	//接收广播数据
    public void update(double temp,double humidity,double pressure);
}

//展示接口(封装变化,具体的数据处理接口)
public interface DisplayElement {
    public void display();
}

//观察者(实现了Observer接口便有资格成为观察者,调用registerObserver方法后注册成功即成观察者)
public class CurrentConditionDisplay implements Observer,DisplayElement {
    private  double temp;
    private  double humidity;
    //持有订阅主题的引用,以便订阅
    private Subject weatherData;

    public CurrentConditionDisplay(Subject weatherData) {
    	//构造器中,传入主题的引用,进行成员变量的初始化
        this.weatherData = weatherData;
        //调用主题的注册方法,进行数据订阅
        weatherData.registerObserver(this);
    }

    @Override
    //数据处理的具体实现
    public void display() {
        System.out.println("Current condition:"+temp+"F degrees and "+humidity+"% humidity");
    }

    @Override
    public void update(double temp, double humidity, double pressure) {
    this.temp=temp;
    this.humidity=humidity;
    //获取最新数据后,封装到消费者中,然后调用数据处理方法进行数据处理
    display();
    }
}

另外两个布告板就不再贴代码了,一样的思路,输出代码也不再展示,看懂了测试便很简单了。

定义:

观察者模式--在对象之间定义一对多得依赖,如此,当一个对象改变状态,依赖它的对象都会收到通知,并自动更新。

讨论:

1、CurrentConditionDisplay实现了DisplayElement 接口,如果有其他的具体类实现DisplayElement 接口,同时功能是一样的,那
岂不是会造成代码的冗余,无法复用的问题?如何解决此问题,可以看上一篇策略模式来解决。

策略模式跳板

2、通过代码可以看出,主题广播数据的方式是通过循环集合中的消费者来调取update将数据传输至消费者中的,在观察者模式中
称之为推送。还有另外一种在文中没有体现的:不管主题数据是否有变化,想要得到最新数据,便主动的去拉取数据,另一种消费
者获取数据的方式。JDK内置有观察者模式,支持推、拉两种数据获取方式都支持,这里不再赘述,如有论述,将在下篇文章中进
行书写。

你可能感兴趣的:(设计模式)