• 读headFirst设计模式


    上次学习了策略模式,这次来学习观察者模式。这次先把书上的例子学习一下,然后再自己写一个例子,看是否能做到举一反三(或者说触类旁通),不过要想真正的掌握还要多多思考和练习。

    学习书上的例子

    现在我们有一个任务,需要根据天气状况来发布不同的布告,开始有3个布告板:当前状况,气象统计,天气预报。像这样的:

     

    现在有一个天气情况的类WeatherData,可以设置和获取温度temperature,湿度humidity和气压pressure数据。要在天气变化时通知布告板,布告板更新以显示不同的值。开始可能想要这样做

    我们以后需要添加新的布告板或者删除布告板,这就要求系统具有弹性,如果是按照上图这样做,每添加或删除布告板都需要修改WeatherData类。好吧,既然这样,我想我们需要来使用观察者模式了。

     认识观察者模式

    加入用户订阅了报纸,报社只要有新的报纸就会给用户送过去,当用户不想看报纸了,就取消订阅,报社就不会送新的报纸过去了。在这里就是一个观察者模式,不过需要改一个名字:报社称为“主题”,用户称为“观察者”。只要主题有更新就会通知观察者,观察者然后更新自身状态。想想天气的例子是不是很类似,所以我们把观察者模式运用在天气的例子中,于是在我们的天气的例子中,天气状况就是主题,布告板就是观察者,只要天气状况发生改变就会通知布告板,然后布告板收到通知并改变自身状态。

    定义观察者模式

    在真实世界中,你通常会看到观察者模式被定义成:定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。

    观察者模式类图

    通常观察者模式被设计成包含Subject和Observer接口。看一下类图

    软件设计原则

    观察者模式通过实现接口的方式实现针对接口编程(这也是一种设计原则,上次说过,这里的接口不一定是interface关键字关键字修饰的接口)建立更有弹性的系统,使对象间的互相依赖降到了最低,这就是所说的松耦合。所以这里的设计原则是:为了对象之间的松耦合而努力。

    使用观察者模式来写天气的例子

    观察者接口Observer:

    1 /**
    2  * 观察者中只有一个update方法
    3  */
    4 public interface Observer {
    5     void update(float temp, float humidity, float pressure);
    6 }
    View Code

    这里update的参数是温度等一个个具体的值,这样不太好,在后面换成对象会好些。

    主题接口Subject:

    /**
     * 主题接口
     */
    public interface Subject {
        void registerObserver(Observer observer);
        void removeObserver(Observer observer);
        void notifyObservers();
    }
    View Code

    显示布告板内容的接口DisplayElement:

    /**
     * 显示布告板内容的接口
     */
    public interface DisplayElement {
        void display();
    }
    View Code

    天气状况WeatherData类实现主题接口Subject,所以WeatherData就是一个主题,当天气发生改变时通知所有的观察者

    具体的主题WeatherData类:

    public class WeatherData implements Subject {
        private ArrayList<Observer> observers = new ArrayList<Observer>();//存储观察者
        private float temperature;    //温度
        private float humidity;        //湿度
        private float pressure;        //气压
    
        @Override
        public void registerObserver(Observer observer) {
            observers.add(observer);
        }
    
        @Override
        public void removeObserver(Observer observer) {
            observers.remove(observer);
        }
    
        //通知所有观察者
        @Override
        public void notifyObservers() {
            for (int i = 0, j = observers.size(); i < j; i++) {
                observers.get(i).update(temperature, humidity, pressure);
            }
        }
        
        //测量值发生改变
        private void measurementsChanged() {
            notifyObservers();
        }
    
        //设置测量值
        public void setMeasurements(float temperature, float humidity, float pressure) {
            this.temperature = temperature;
            this.humidity = humidity;
            this.pressure = pressure;
            measurementsChanged();
        }
    }
    View Code

    具体的观察者:当前状况CurrentConditionDisplay类

    public class CurrentConditionDisplay implements Observer, DisplayElement {
        private float temperature;    //温度
        private float humidity;        //湿度
        private float pressure;        //气压
        private Subject weatherData;
        
        //创建观察者时传入主题对象,将自己注册成观察者
        public CurrentConditionDisplay(Subject weatherData) {
            this.weatherData = weatherData;
            //将自己注册成观察者
            weatherData.registerObserver(this);
        }
        
        //移除观察者
        public void removeObserver() {
            weatherData.removeObserver(this);
        }
    
        @Override
        public void display() {
            System.out.println(toString());
        }
    
        @Override
        public void update(float temp, float humidity, float pressure) {
            this.temperature = temp;
            this.humidity = humidity;
            this.pressure = pressure;
            display();
        }
    
        @Override
        public String toString() {
            return "CurrentConditionDisplay [temperature=" + temperature
                    + ", humidity=" + humidity + ", pressure=" + pressure + "]";
        }
    }
    View Code

    这里怎么显示或利用这些数据可以自己设计

    测试一下:

    public class ObserverTest {
        public static void main(String[] args) {
            //创建一个具体的主题
            WeatherData weatherData = new WeatherData();
            //创建一个具体的观察者
            CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(weatherData);
            weatherData.setMeasurements(23, 45, 1);
            weatherData.setMeasurements(28, 31, 1);
            weatherData.setMeasurements(36, 12, 1);
            currentConditionDisplay.removeObserver();//移除
            weatherData.setMeasurements(15, 21, 1);
        }
    }
    View Code

    你会看到每当天气改变时布告板会收到通知并自动更新

    再附加一个气象统计的类,其他的类和测试类自己写一下

    /**
     * 获取平均值
     */
    public class WeatherStatistics implements Observer, DisplayElement {
        private static final float[] tempArr = new float[2];    //分别存贮次数和总数
        private static final float[] humidityArr = new float[2];
        private static final float[] pressureArr = new float[2];
        
        private float temperature;
        private float humidity;
        private float pressure; 
        
        //创建对象自动注册为观察者
        public WeatherStatistics(Subject weatherData) {
            weatherData.registerObserver(this);
        }
        
        @Override
        public void display() {
            System.out.println(toString());
        }
    
        @Override
        public void update(float temp, float humidity, float pressure) {
            average(temp, humidity, pressure);
            display();
        }
    
        private void average(float temp, float humidity, float pressure) {
            tempArr[0] = tempArr[0] + 1;
            tempArr[1] = tempArr[1] + temp;
            humidityArr[0] = humidityArr[0] + 1;
            humidityArr[1] = humidityArr[1] + humidity;
            pressureArr[0] = pressureArr[0] + 1;
            pressureArr[1] = pressureArr[1] + pressure;
            
            this.temperature = tempArr[1] / tempArr[0];
            this.humidity = humidityArr[1] / humidityArr[0];
            this.pressure = pressureArr[1] / pressureArr[0];
        }
    
        @Override
        public String toString() {
            return "WeatherStatistics [temperature=" + temperature + ", humidity="
                    + humidity + ", pressure=" + pressure + "]";
        }
    }
    View Code

    这样我们就设计了自己的给观察者模式,其实jdk中已经内置了观察者模式,位于java.util包下,与我们自己设计的有些不同,主题是继承Observable类,这时可以称之为“可观察者”,里面也有添加删除观察者的方法。观察者实现Observer接口,可观察者要如何送出通知呢?需要下面的步骤:

    推荐看一下源码,会更加清晰

    现在用jdk内置的观察者模式修改一下天气的例子

    WeatherData:

    import java.util.Observable;
    /**
     * Observable 可观察者,即以前的主题
     */
    public class WeatherData extends Observable {
        private float temperature;
        private float humidity;
        private float pressure;
        
        public WeatherData() {}
    
        public void setMeasurements(float temperature, float humidity, float pressure) {
            this.temperature = temperature;
            this.humidity = humidity;
            this.pressure = pressure;
            measurementsChanged();
        }
    
        //测量值发生改变时
        private void measurementsChanged() {
            setChanged();
            notifyObservers();
        }
    
        public float getTemperature() {
            return temperature;
        }
    
        public float getHumidity() {
            return humidity;
        }
    
        public float getPressure() {
            return pressure;
        }
    }
    View Code

    CurrentConditionDisplay:

    /**
     * 观察者,实现了观察者接口
     */
    public class CurrentConditionDisplay implements Observer, DisplayElement {
        private Observable observable;
        private float temperature;
        private float humidity;
        private float pressure;
    
        public CurrentConditionDisplay(Observable observable) {
            this.observable = observable;
            observable.addObserver(this);//添加为可观察者中的观察者
        }
    
        @Override
        public void update(Observable o, Object arg) {
            WeatherData weatherData = (WeatherData) o;
            this.temperature = weatherData.getTemperature();
            this.humidity = weatherData.getHumidity();
            this.pressure = weatherData.getPressure();
            display();
        }
    
        @Override
        public void display() {
            System.out.println(toString());
        }
    
        @Override
        public String toString() {
            return "CurrentConditionDisplay [temperature=" + temperature
                    + ", humidity=" + humidity + ", pressure=" + pressure + "]";
        }
    }
    View Code

    这个和我们刚才自己设计的不同:把主题(可观察者)传给了观察者。

    测试一下:

    public class ObserverTest {
        public static void main(String[] args) {
            WeatherData weatherData = new WeatherData();
            CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(weatherData);
            weatherData.setMeasurements(12, 24, 1.3F);
            weatherData.setMeasurements(20, 28, 1F);
        }
    }
    View Code

    附另外一个是否适合出行的布告板GoingDisplay:

    public class GoingDisplay implements Observer, DisplayElement {
        private Observable observable;
        private float temperature;
        private float humidity;
        private float pressure;
        
        public GoingDisplay(Observable observable) {
            this.observable = observable;
            observable.addObserver(this);    //添加为观察者
        }
    
        @Override
        public void display() {
            boolean isFitGoing = fitGoing();
            System.out.println(isFitGoing ? "适宜出行" : "不适合外出");
        }
    
        private boolean fitGoing() {
            if (temperature > 15 && temperature < 30 && humidity >= 20 && pressure == 1)
                return true;
            return false;
        }
    
        @Override
        public void update(Observable o, Object arg) {
            WeatherData weatherData = (WeatherData) o;
            this.temperature = weatherData.getTemperature();
            this.humidity = weatherData.getHumidity();
            this.pressure = weatherData.getPressure();
            display();
        }
    }
    View Code

    好了,到这里我们把书上关于观察者模式的内容学完了,关于jdk内置的观察者模式,还是建议看一下源码。好了,书上的例子是一个主题和多个观察者,现在我们自己来写多个主题和一个观察者的例子。比如一个用户订阅了一个科技网站,然后还关注了一个电视剧,如果网站有新的咨询,电视剧有更新都会通知这个用户,这就符合观察者模式了,我们来做一下。不用jdk内置的,自己设计。

    Observer接口改一下,以主题作为参数

    public interface Observer {
        void update(Subject subject);
    }
    View Code

    Subject接口和DisplayElement接口和前面的一样

    具体的主题:科技网站Website

    /**
     * 一个网站, 有资讯和博客,如果你订阅了或关注了,当有更新时,你会收到更新
     */
    public class Website implements Subject {
        private List<Observer> observerList = new ArrayList<Observer>();
        private static boolean changed = false;
        private String message;
        private String blog;
    
        @Override
        public boolean addObserver(Observer observer) {
            return observerList.add(observer);
        }
    
        @Override
        public boolean removeObserver(Observer observer) {
            return observerList.remove(observer);
        }
    
        @Override
        public void notifyObservers() {
            if (observerList != null && observerList.size() > 0) {
                for (Observer observer : observerList) {
                    observer.update(this);
                }
            }
        }
        
        //借鉴jdk内置观察者模式设置一个标记,一边选择性的通知观察者
        public void setChanged() {
            changed = true;
        }
        
        //内容更新时通知观察者
        public void setContent(String message, String blog) {
            this.message = message;
            this.blog = blog;
            if (changed)
                notifyObservers();
            changed = false;
        }
    
        @Override
        public String toString() {
            return "您订阅的网站有更新: Website [message=" + message + ", blog=" + blog + "]";
        }
    }
    View Code

    类似Website的电视剧TV:

    /**
     * 电视剧作为一个主题,当有更新时通知用户
     */
    public class TV implements Subject {
        private List<Observer> observerList = new ArrayList<Observer>();
        private static boolean changed = false;
        private Date updateDate;//更新日期
        private Integer episodeNum;//更新到了哪一集
    
        @Override
        public boolean addObserver(Observer observer) {
            return observerList.add(observer);
        }
    
        @Override
        public boolean removeObserver(Observer observer) {
            return observerList.remove(observer);
        }
    
        @Override
        public void notifyObservers() {
            if (observerList != null && observerList.size() > 0) {
                //通知所有观察者
                for (Observer observer : observerList) {
                    observer.update(this);
                }
            }
        }
        
        public void setChanged() {
            changed = true;
        }
        
        //剧集更新时通知所有观察者
        /**
         * 什么时候更新到了第多少集
         * @param updateDate
         * @param episodeNum
         */
        public void episodeUpdate(Date updateDate, Integer episodeNum) {
            this.updateDate = updateDate;
            this.episodeNum = episodeNum;
            if (changed) 
                notifyObservers();
            changed = false;
        }
    
        @Override
        public String toString() {
            return "您关注的电视剧更新啦,去看吧-=TV [updateDate=" + new SimpleDateFormat("yyyy-MM-dd").format(updateDate) + ", episodeNum=" + episodeNum
                    + "]";
        }
    }
    View Code

    具体的观察者User:

    /**
     * 具体的观察者日,这个没有让其创建时自动注册为观察者,而是调用方法成为观察者,模拟用户自主订阅或关注
     */
    public class User implements Observer, Display {
        private Subject subject;
    
        public User(Subject subject) {
            this.subject = subject;
        }
    
        @Override
        public void display() {
            if (subject instanceof Website) {
                Website w = (Website) subject;
                System.out.println(w.toString());
            } else if (subject instanceof TV) {
                TV tv = (TV) subject;
                System.out.println(tv.toString());
            }
        }
    
        @Override
        public void update(Subject subject) {
            //主题把自身当作参数传过来 
            this.subject = subject;
            display();
        }
        
        //订阅或关注,这样自己就成了观察者
        public boolean subscribe() {
            return subject.addObserver(this);
        }
    
        //取消订阅或关注,这样自己从观察者中删除了
        public boolean unSubscribe() {
            return subject.removeObserver(this);
        }
    }
    View Code

    测试一下:

    /**
     * 试一下注释掉的代码
     */
    public class ObserverTest {
        public static void main(String[] args) {
            run1();
        }
        
        public static void run1() {
            Website website = new Website();
            TV tv = new TV();
            User user_website = new User(website);
            User user_tv = new User(tv);
            //订阅,使自己成为观察者
            user_website.subscribe();
            user_tv.subscribe();
    //        user_website.unSubscribe();//取消订阅
            
            website.setChanged();
            website.setContent("new message1", "new blog1");
            
    //        tv.setChanged();
            tv.episodeUpdate(new Date(), 26);
        }
    }
    View Code

    好了,这次讲解就这么多了,大家(当然,包括我)要想掌握观察者模式,还需要多多思考练习。

  • 相关阅读:
    jdk9以上配置远程断点调试debug
    记解决grpc报错:HTTP/2 client preface string missing or corrupt. Hex dump for received bytes
    CENTOS7静默安装ORACLE11G及数据泵迁移
    数据链路层(7) 链路层设备
    数据链路层(6) 局域网 无线局域网 广域网
    数据链路层(5) 动态分配信道 ALOHA协议、CSMA协议、CSMA/CD协议、CSMA/CA
    数据链路层(3) 流量控制
    数据链路层(2) 差错控制
    数据链路层(1) 数据链路层基本概念
    数据链路层(4) 静态划分信道
  • 原文地址:https://www.cnblogs.com/pdzbokey/p/6489426.html
Copyright © 2020-2023  润新知