zoukankan      html  css  js  c++  java
  • 读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

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

  • 相关阅读:
    How to create jar for Android Library Project
    Very large tabs in eclipse panes on Ubuntu
    64bit Ubuntu, Android AAPT, R.java
    Linux(Ubuntu)下如何安装JDK
    Configure xterm Fonts and Colors for Your Eyeball
    建立、配置和使用Activity——启动其他Activity并返回结果
    建立、配置和使用Activity——使用Bundle在Activity之间交换数据
    建立、配置和使用Activity——启动、关闭Activity
    建立、配置和使用Activity——Activity
    异步任务(AsyncTask)
  • 原文地址:https://www.cnblogs.com/pdzbokey/p/6489426.html
Copyright © 2011-2022 走看看