• 观察者模式


    基本介绍

    观察者模式(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(观察者)而设计,类图如下

     

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

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

     

    主题接口

    /**
     * 主题
     */
    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();
    	}
    }
    

    输出结果

     

    观察者模式(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();
        }
    }
    

    输出结果

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

  • 相关阅读:
    linux下,webpack热重载无效的解决方法
    前端异步编程之Promise和async的用法
    防呆设计(内容摘录)
    GUI 图形用户界面 [学习笔记]
    15条JavaScript最佳实践【转】
    2013-11-02 【webrebuild广州站】分享会纪要
    关于自控力
    记录一次抖音小程序严重bug(组件样式继承问题)
    微信 头条小程序 记录一次电商项目倒计时活动优化
    微信/头条小程序如何确保异步请求执行完后再执行各页面的onLoad方法
  • 原文地址:https://www.cnblogs.com/dwlovelife/p/13352651.html
Copyright © 2020-2023  润新知