观察者模式

声明 本文内容属于《Head First 设计模式》阅读笔记,文中涉及到的知识案例等直接或间接来源于该书。《Head First 设计模式》通过有趣的图表+文字的形式,让人自然学习设计模式,非常棒推荐阅读


观察者模式概念

        定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新

图示说明

观察者模式_第1张图片注:观察者模式是所有设计模式中应用最多、最广的设计模式,不论是MVC等框架,还是监听器、MQ等等,都有用到观察者模式。


案例(辅助理解)

提示 观察者模式使用灵活,下面只是一个简单的例子,在不同的情境下,可能有不同的表现形式。

情景介绍

       现在有一个气象站,其能实时提供变化了的气象数据;同时有三个公告板,分别要根据气象站的气象数据信息,显示:实时的气象信息、统计后的气象信息(如:统计温度平均值等等)、气象预报信息。

从观察者模式出发

       气象站可视为被观察者Subject,三个公告板可视为三个观察者Observer。再进行基本的抽象-封装-继承/实现-多态,可以有以下UML类图:
观察者模式_第2张图片       其中Subject为被观察者需要实现的接口,其中三个主要的方法分别是:注册(观察者开始观察被观察者)、移除(观察者取消观察被观察者)、通知(被观察者发送消息给观察者);Observer为观察者需要实现的接口,只有一个方法:更新(被观察者发送消息后,会走此方法进行各个观察者的逻辑操作)。
注:这只是最基本的方法示例,可以根据实际业务情况灵活变通或对其进行拓展。对观察者模式理念切实的理解,还需要借助一下具体实现类,下面会给出代码及测试示例,以供进一步理解此模式。

上述观察者模式示例中的几个核心类

  • Subject:

    import com.szlaozicl.designpattern.observer.observer.Observer;
    
    /**
     * 主题(即:可/被观察者)
     *
     * 注:所有被观察者均应实现此接口。
     *
     * @author JustryDeng
     * @date 2019/10/12 11:53
     */
    public interface Subject {
    
        /**
         * 注册(新增)观察者
         *
         * @param o
         *            要注册的观察者对象
         *
         * @return 是否注册成功
         * @date 2019/10/12 11:53
         */
        boolean registerObserver(Observer o);
    
        /**
         * 注销(移除)观察者
         *
         * @param o
         *            要注销的观察者对象
         *
         * @return 是否注销成功
         * @date 2019/10/12 11:53
         */
        boolean removeObserver(Observer o);
    
        /**
         * 通知观察者们
         *
         * @date 2019/10/12 11:53
         */
        void notifyObservers();
    }
    
  • WeatherData:

    import com.szlaozicl.designpattern.observer.observer.Observer;
    import com.szlaozicl.designpattern.observer.subject.Subject;
    import lombok.Data;
    
    import java.util.ArrayList;
    
    /**
     * 气象数据
     *
     * @author JustryDeng
     * @date 2019/10/12 12:09
     */
    @Data
    public class WeatherData implements Subject {
    
        /** 温度 */
        private float temperature;
    
        /** 湿度 */
        private float humidity;
    
        /** 气压(千帕) */
        private float pressure;
    
        /**
         * 观察者容器
         *
         * 注: 观察者的 注册 与 注销, 操作此集合即可
         */
        private ArrayList<Observer> observers = new ArrayList<>(8);
    
        @Override
        public boolean registerObserver(Observer o) {
            return observers.add(o);
        }
    
        @Override
        public boolean removeObserver(Observer o) {
            int index = observers.indexOf(o);
            if (index >= 0) {
                observers.remove(index);
            }
            return true;
        }
    
        @Override
        public void notifyObservers() {
            for (Observer observer : observers) {
                // 这里采用: 观察者主动从 被观察者拉数据的方式
                // void update(Subject subject, Object... args);
                observer.update(this);
            }
        }
    
        /**
         * 当参数值改变时,会调用此方法
         *
         * @date 2019/10/12 12:33
         */
        private void measurementsChanged() {
            notifyObservers();
        }
    
        /**
         * 设置参数值
         *
         * @param temperature
         *            温度
         * @param humidity
         *            湿度
         * @param pressure
         *            气压
         *
         * @date 2019/10/12 12:33
         */
        public void setMeasurements(float temperature, float humidity, float pressure) {
            boolean haveChange = this.temperature != temperature
                    || this.humidity != humidity || this.pressure != pressure;
            if (haveChange) {
                this.temperature = temperature;
                this.humidity = humidity;
                this.pressure = pressure;
                measurementsChanged();
            }
    
        }
    
    }
    
  • Observer:

    import com.szlaozicl.designpattern.observer.subject.Subject;
    
    /**
     * 观察者
     *
     * 注:所有观察者均应实现此接口。
     *
     * @author JustryDeng
     * @date 2019/10/12 11:57
     */
    public interface Observer {
    
        /**
         * 更新
         *
         *  注: 当被观察者发生变化时, 有两种方式将数据 从主题传递给观察者
         *      1. 被观察者 主动传参给 观察者, 即: 被观察者主动推数据给观察者
         *      2. 观察者 通过调用 被观察者的一些方法(如:getter)主动获取相关数据, 即:观察者主动从被观察者拉数据
         *
         * @param subject
         *            主题(即:被观察者对象)
         *            注:用于 观察者主动从被观察者拉数据。
         * @param args
         *            被观察者注定push给观察者的参数
         *            注:用于 被观察者主动推数据给观察者。
         *
         * @date 2019/10/12 12:00
         */
        void update(Subject subject, Object... args);
    }
    
  • ConcreteObserver:

    import com.szlaozicl.designpattern.observer.observer.Observer;
    import com.szlaozicl.designpattern.observer.other.Display;
    import com.szlaozicl.designpattern.observer.subject.Subject;
    import com.szlaozicl.designpattern.observer.subject.impl.WeatherData;
    import lombok.Data;
    
    /**
     * 实时信息 观察者
     *
     * @author JustryDeng
     * @date 2019/10/12 12:48
     */
    @Data
    public class ConcreteObserver implements Observer, Display {
    
        /** 温度 */
        private float temperature;
    
        /** 湿度 */
        private float humidity;
    
        /** 气压(千帕) */
        private float pressure;
    
        @Override
        public void update(Subject subject, Object... args) {
            if (subject instanceof WeatherData) {
                WeatherData weatherData = (WeatherData)subject;
                this.temperature = weatherData.getTemperature();
                this.humidity = weatherData.getHumidity();
                this.pressure = weatherData.getPressure();
            }
            display();
        }
    
        @Override
        public void display() {
            String info = "\n【实时信息观察者ConcreteObserver】观察到的数据是:\n"
                    .concat("\t当前温度: ").concat(String.valueOf(temperature)).concat("\n")
                    .concat("\t当前湿度: ").concat(String.valueOf(humidity)).concat("\n")
                    .concat("\t当前气压: ").concat(String.valueOf(pressure)).concat("\n");
            System.out.println(info);
        }
    }
    

注:观察者ForecastObserver与观察者StatisticsObserver的代码,这里就不再给出了,可详见文末完整代码托管链接。

测试一下

  • 测试类:
    import com.szlaozicl.designpattern.observer.observer.impl.ConcreteObserver;
    import com.szlaozicl.designpattern.observer.observer.impl.ForecastObserver;
    import com.szlaozicl.designpattern.observer.observer.impl.StatisticsObserver;
    import com.szlaozicl.designpattern.observer.subject.impl.WeatherData;
    
    import java.util.concurrent.TimeUnit;
    
    /**
     * 测试 --- 观察者模式
     *
     * @author JustryDeng
     * @date 2019/10/12 13:23
     */
    public class Test {
    
        /**
         * 函数入口
         */
        public static void main(String[] args) throws InterruptedException {
            /// 创建 被观察者
            WeatherData weatherData = new WeatherData();
    
            /// 创建 观察者们
            ConcreteObserver concreteObserver = new ConcreteObserver();
            ForecastObserver forecastObserver = new ForecastObserver();
            StatisticsObserver statisticsObserver = new StatisticsObserver();
    
            /// 观察者 进行注册,开始观察 被观察者
            boolean resultOne = weatherData.registerObserver(concreteObserver);
            boolean resultTwo = weatherData.registerObserver(forecastObserver);
            boolean resultThree = weatherData.registerObserver(statisticsObserver);
            System.out.println("concreteObserver注册观察weatherData, " + (resultOne ? "成功!" : "失败!"));
            System.out.println("forecastObserver注册观察weatherData, " + (resultTwo ? "成功!" : "失败!"));
            System.out.println("registerObserver注册观察weatherData, " + (resultThree ? "成功!" : "失败!"));
    
            /// ------------------------ 测试验证
            // 被观察者 第一次数据发生变化
            TimeUnit.MILLISECONDS.sleep(100);
            System.err.println("\n被观察者 第一次 数据发生变化!");
            weatherData.setMeasurements(10.5F, 0.23F, 50.2F);
    
    
    
            // 被观察者 第二次数据发生变化
            TimeUnit.MILLISECONDS.sleep(100);
            System.err.println("被观察者 第二次 数据发生变化!");
            weatherData.setMeasurements(27.5F, 0.68F, 100.2F);
    
            /// 观察者 取消观察
            boolean resultFour = weatherData.removeObserver(concreteObserver);
            boolean resultFive = weatherData.removeObserver(forecastObserver);
            boolean resultSix = weatherData.removeObserver(statisticsObserver);
            System.out.println("concreteObserver取消观察weatherData, " + (resultFour ? "成功!" : "失败!"));
            System.out.println("forecastObserver取消观察weatherData, " + (resultFive ? "成功!" : "失败!"));
            System.out.println("registerObserver取消观察weatherData, " + (resultSix ? "成功!" : "失败!"));
    
            // 被观察者 第三次数据发生变化
            TimeUnit.MILLISECONDS.sleep(100);
            System.err.println("\n被观察者 第三次 数据发生变化!");
            weatherData.setMeasurements(30.5F, 0.88F, 680.2F);
        }
    }
    
  • 运行测试类main方法,控制台输出:
    观察者模式_第3张图片

观察者模式学习完毕 !


^_^ 如有不当之处,欢迎指正

^_^ 参考资料
        《Head First 设计模式》
Eric Freeman & Elisabeth Freeman with Kathy Sierra & Bert Bates著,O’Reilly Taiwan公司译,UMLChina改编

^_^ 测试代码托管链接
         https://github.com/JustryDeng…DesignPattern

^_^ 本文已经被收录进《程序员成长笔记(六)》,笔者JustryDeng

你可能感兴趣的:(观察者模式,观察者模式示例,观察者,设计模式,设计模式之观察者模式,Head,First设计模式)