资讯专栏INFORMATION COLUMN

观察者模式(ObserverPattern)

JeOam / 1976人阅读

摘要:当气象局发布新的天气数据后,两个公告牌上显示的天气数据必须实时更新。气象局同时要求我们保证程序拥有足够的可扩展性,因为后期随时可能要新增新的公告牌。

转载请注明出处:https://zhuanlan.zhihu.com/p/20540213
文章中的例子和思路均来自于《Head First》

场景

我们接到一个来自气象局的需求:气象局需要我们构建一套系统,这系统有两个公告牌,分别用于显示当前的实时天气和未来几天的天气预报。当气象局发布新的天气数据(WeatherData)后,两个公告牌上显示的天气数据必须实时更新。气象局同时要求我们保证程序拥有足够的可扩展性,因为后期随时可能要新增新的公告牌。

概况

这套系统中主要包括三个部分:气象站(获取天气数据的物理设备)、WeatherData(追踪来自气象站的数据,并更新公告牌)、公告牌(用于展示天气数据)

WeatherData知道如何跟气象站联系,以获得天气数据。当天气数据有更新时,WeatherData会更新两个公告牌用于展示新的天气数据。

错误示范

我们现来看看隔壁老王的实现思路:

public class WeatherData {

    //实例变量声明
    ...
    
    public void measurementsChanged() {
    
        float temperature = getTemperature();
        float humidity = getHumidity();
        float pressure = getPressure();
        List forecastTemperatures = getForecastTemperatures();
        
        //更新公告牌
        currentConditionsDisplay.update(temperature, humidity, pressure);
        forecastDisplay.update(forecastTemperatures);
    }
    ...
}

上面这段代码是典型的针对实现编程,这会导致我们以后增加或删除公告牌时必须修改程序。我们现在来看看观察者模式,然后再回来看看如何将观察者模式应用到这个程序。

观察者模式介绍

观察者模式面向的需求是:A对象(观察者)对B对象(被观察者)的某种变化高度敏感,需要在B变化的一瞬间做出反应。举个例子,新闻里喜闻乐见的警察抓小偷,警察需要在小偷伸手作案的时候实施抓捕。在这个例子里,警察是观察者、小偷是被观察者,警察需要时刻盯着小偷的一举一动,才能保证不会错过任何瞬间。程序里的观察者和这种真正的【观察】略有不同,观察者不需要时刻盯着被观察者(例如A不需要每隔1ms就检查一次B的状态),二是采用__注册__(_Register_)或者成为__订阅__(_Subscribe_)的方式告诉被观察者:我需要你的某某状态,你要在它变化时通知我。采取这样被动的观察方式,既省去了反复检索状态的资源消耗,也能够得到最高的反馈速度。

观察者模式通常基于__Subject__和__Observer__接口类来设计,下面是是类图:

观察者模式的应用

结合上面的类图,我们现在将观察者模式应用到WeatherData项目中来。于是有了下面这张类图:

主题接口

/**
 * 主题(发布者、被观察者)
 */
public interface Subject {

    /**
     * 注册观察者
     */
    void registerObserver(Observer observer);

    /**
     * 移除观察者
     */
    void removeObserver(Observer observer);

    /**
     * 通知观察者
     */
    void notifyObservers(); 
}

观察者接口

/**
 * 观察者
 */
public interface Observer {
    void update();
}

公告牌用于显示的公共接口

public interface DisplayElement {
    void display();
}

下面我们再来看看WeatherData是如何实现的

public class WeatherData implements Subject {

    private List observers;

    private float temperature;//温度
    private float humidity;//湿度
    private float pressure;//气压
    private List forecastTemperatures;//未来几天的温度

    public WeatherData() {
        this.observers = new ArrayList();
    }

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

    @Override
    public void removeObserver(Observer observer) {
        this.observers.remove(observer);
    }

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

    public void measurementsChanged() {
        notifyObservers();
    }

    public void setMeasurements(float temperature, float humidity, 
    float pressure, List forecastTemperatures) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        this.forecastTemperatures = forecastTemperatures;
        measurementsChanged();
    }

    public float getTemperature() {
        return temperature;
    }

    public float getHumidity() {
        return humidity;
    }

    public float getPressure() {
        return pressure;
    }

    public List getForecastTemperatures() {
        return forecastTemperatures;
    }
}

显示当前天气的公告牌CurrentConditionsDisplay

public class CurrentConditionsDisplay implements Observer, DisplayElement {

    private WeatherData weatherData;

    private float temperature;//温度
    private float humidity;//湿度
    private float pressure;//气压

    public CurrentConditionsDisplay(WeatherData weatherData) {
        this.weatherData = weatherData;
        this.weatherData.registerObserver(this);
    }

    @Override
    public void display() {
        System.out.println("当前温度为:" + this.temperature + "℃");
        System.out.println("当前湿度为:" + this.humidity);
        System.out.println("当前气压为:" + this.pressure);
    }

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

显示未来几天天气的公告牌ForecastDisplay

public class ForecastDisplay implements Observer, DisplayElement {

    private WeatherData weatherData;

    private List forecastTemperatures;//未来几天的温度

    public ForecastDisplay(WeatherData weatherData) {
        this.weatherData = weatherData;
        this.weatherData.registerObserver(this);
    }

    @Override
    public void display() {
        System.out.println("未来几天的气温");
        int count = forecastTemperatures.size();
        for (int i = 0; i < count; i++) {
            System.out.println("第" + i + "天:" + forecastTemperatures.get(i) + "℃");
        }
    }

    @Override
    public void update() {
        this.forecastTemperatures = this.weatherData.getForecastTemperatures();
        display();
    }
}

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

观察者模式将观察者和主题(被观察者)彻底解耦,主题只知道观察者实现了某一接口(也就是Observer接口)。并不需要观察者的具体类是谁、做了些什么或者其他任何细节。任何时候我们都可以增加新的观察者。因为主题唯一依赖的东西是一个实现了Observer接口的对象列表。

好了,我们测试下利用观察者模式重构后的程序:

public class ObserverPatternTest {

    public static void main(String[] args) {

        WeatherData weatherData = new WeatherData();
        CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData);
        ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);

        List forecastTemperatures = new ArrayList();
        forecastTemperatures.add(22f);
        forecastTemperatures.add(-1f);
        forecastTemperatures.add(9f);
        forecastTemperatures.add(23f);
        forecastTemperatures.add(27f);
        forecastTemperatures.add(30f);
        forecastTemperatures.add(10f);
        weatherData.setMeasurements(22f, 0.8f, 1.2f, forecastTemperatures);
    }
}

输出结果:

当前温度为:22.0℃
当前湿度为:0.8
当前气压为:1.2
未来几天的气温
第0天:22.0℃
第1天:-1.0℃
第2天:9.0℃
第3天:23.0℃
第4天:27.0℃
第5天:30.0℃
第6天:10.0℃

源码地址:https://github.com/BaronZ88/DesignPatterns/tree/master/src/com/baron/patterns/observer

如果大家喜欢这一系列的文章,欢迎关注我的知乎专栏和GitHub。

知乎专栏:https://zhuanlan.zhihu.com/baron

GitHub:https://github.com/BaronZ88

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/66517.html

相关文章

  • 设计模式察者设计模式

    摘要:关键概念理解观察者设计模式中主要区分两个概念观察者指观察者对象,也就是消息的订阅者被观察者指要观察的目标对象,也就是消息的发布者。 原文首发于微信公众号:jzman-blog,欢迎关注交流! 最近补一下设计模式相关的知识,关于观察者设计模式主要从以下几个方面来学习,具体如下: 什么是观察者设计模式 关键概念理解 通知观察者的方式 观察者模式的实现 观察者模式的优缺点 使用场景 下面...

    NotFound 评论0 收藏0
  • 设计模式察者模式与发布订阅模式

    摘要:观察者模式与发布订阅的区别在模式中,知道,同时还保留了的记录。发布者订阅者在大多情况下是异步方式使用消息队列。图片源于网络侵权必删如果以结构来分辨模式,发布订阅模式相比观察者模式多了一个中间件订阅器,所以发布订阅模式是不同于观察者模式的。 学习了一段时间设计模式,当学到观察者模式和发布订阅模式的时候遇到了很大的问题,这两个模式有点类似,有点傻傻分不清楚,博客起因如此,开始对观察者和发布...

    BaronZhang 评论0 收藏0
  • 设计模式 -- 察者模式

    摘要:总结一下从表面上看观察者模式里,只有两个角色观察者被观察者而发布订阅模式,却不仅仅只有发布者和订阅者两个角色,还有第三个角色经纪人存在。参考链接观察者模式发布订阅模式 做了这么长时间的 菜鸟程序员 ,我好像还没有写过一篇关于设计模式的博客...咳咳...意外,纯属意外。所以,我决定,从这一刻起,我要把设计模式在从头学习一遍,不然都对不起我这 菜鸟 的身份。那这次,就从观察者模式开始好啦...

    chengtao1633 评论0 收藏0
  • 设计模式 -- 察者模式

    摘要:总结一下从表面上看观察者模式里,只有两个角色观察者被观察者而发布订阅模式,却不仅仅只有发布者和订阅者两个角色,还有第三个角色经纪人存在。参考链接观察者模式发布订阅模式 做了这么长时间的 菜鸟程序员 ,我好像还没有写过一篇关于设计模式的博客...咳咳...意外,纯属意外。所以,我决定,从这一刻起,我要把设计模式在从头学习一遍,不然都对不起我这 菜鸟 的身份。那这次,就从观察者模式开始好啦...

    makeFoxPlay 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<