• [设计模式学习笔记] -- 观察者模式


    观察者模式

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

      出版者+订阅者=观察者模式。以报社与订报人为例:

    1. 报社出版报纸。
    2. 你向这家报社订阅报纸,只要他们有新版报纸出版,就会给你送一份。
    3. 当你不想再看报纸的时候,取消订阅,报社就不会在给你送报纸。

      举个简单的例子来描述观察者模式。

      某某市要建立空气质量站,购买了最新型的空气质量监测仪来实时监测城市的空气质量。并开放接口将空气质量提供给所有需要的人或公司。大体结构如下:

     

      空气质量站(AirQualityStations)知道如何采集空气质量检测仪监测到的数据,得到数据后发送给PM2.5数据网(PM2d5Display)和空气质量发布网(AQIDisplay)。

      功能很简单,首先看一段实现的代码(错误的示范):

    // 空气质量数据更新。
    // 空气质量监测仪会自动调用该方法,并更新pollutants的数据。
    public void AirQualityChanged() {
        // 将最新的空气质量数据更新给这两个网站。
        pm2d5Display.Update(pollutants);
        aqiDisplay.Update(pollutants);
    }

      在这个实现中,犯了几个错误:

    1. 针对具体实现编程,而非接口。
    2. 只支持两个网站,若加入新网站要修改代码。
    3. 无法在运行时动态的增加或删除网站。
    4. 为封装将来会改变的部分。

      如果使用个观察者模式,将会很好的解决这些问题,观察者模式以松耦合的方式定义了一系列对象之间的一对多关系。当一个对象改变状态,其他依赖者都会收到通知。

    设计原则

    为了交互对象之间的松耦合设计而努力。

      现在,使用观察者模式设计这个需求,类图如下:

      Subject为主题接口,空气质量监测站实现了主题接口。Observer为观察者接口,由要发布空气质量信息的网站所实现,同时也为它们建立了一个显示空气信息的接口。代码如下:

    package cn.net.bysoft.observer;
    
    //    主题接口。
    public interface Subject {
        
        //    定义注册或删除观察者的方法。
        public void registerObserver(Observer o);
        public void removeObserver(Observer o);
        
        //    当主题状态改变时,这个方法会被调用,以通知所有的观察者。
        public void notifyObservers();
    }
    主题接口代码
    package cn.net.bysoft.observer;
    
    import java.util.ArrayList;
    import java.util.List;
    
    //    空气质量检测站。
    public class AirQualityStations implements Subject {
    
        public AirQualityStations() {
            this.observers = new ArrayList<Observer>();
        }
    
        // 添加观察者。
        public void registerObserver(Observer o) {
            observers.add(o);
        }
    
        // 删除观察者。
        public void removeObserver(Observer o) {
            int i = observers.indexOf(o);
            if (i >= 0) {
                observers.remove(i);
            }
        }
    
        // 当空气质量值改变时该函数会被调用。
        public void notifyObservers() {
            for (Observer o : observers) {
                //    更新观察者的数据。
                o.update(this.pollutants);
            }
        }
    
        // 空气质量数据更新。
        // 空气质量监测仪会自动调用该方法,并更新pollutants的数据。
        public void AirQualityChanged() {
            notifyObservers();
        }
    
        public Pollutants getPollutants() {
            return pollutants;
        }
        
        public void setPollutants(Pollutants pollutants) {
            this.pollutants = pollutants;
        }
    
        // 污染物对象。
        private Pollutants pollutants;
        // 观察者集合。
        List<Observer> observers;
    }
    空气质量监测站代码
    package cn.net.bysoft.observer;
    
    //    观察者接口。
    public interface Observer {
        //    当空气质量值改变时,主题会把这些状态值当作方法的参数,传送给观察者。
        public void update(Pollutants pollutants);
    }
    观察者接口代码
    package cn.net.bysoft.observer;
    
    //    显示空气质量接口。
    public interface DisplayElement {
        public void display();
    }
    显示空气质量接口代码
    package cn.net.bysoft.observer;
    
    // 污染物实体类。
    public class Pollutants {
        //    细颗粒物。
        private int PM2d5;
        //    二氧化硫。
        private int SO2;
        //    二氧化氮。
        private int NO2;
        //    臭氧。
        private int O3;
        //    一氧化碳。
        private int CO;
        //    可吸入颗粒物。
        private int PM10;
        
        public int getPM2d5() {
            return PM2d5;
        }
        public void setPM2_5(int pM2d5) {
            PM2d5 = pM2d5;
        }
        public int getSO2() {
            return SO2;
        }
        public void setSO2(int sO2) {
            SO2 = sO2;
        }
        public int getNO2() {
            return NO2;
        }
        public void setNO2(int nO2) {
            NO2 = nO2;
        }
        public int getO3() {
            return O3;
        }
        public void setO3(int o3) {
            O3 = o3;
        }
        public int getCO() {
            return CO;
        }
        public void setCO(int cO) {
            CO = cO;
        }
        public int getPM10() {
            return PM10;
        }
        public void setPM10(int pM10) {
            PM10 = pM10;
        }
    }
    空气中的污染物实体类代码
    package cn.net.bysoft.observer;
    
    import java.util.Date;
    
    //    PM2.5布告板。
    public class PM2d5Display implements Observer, DisplayElement {
        //    污染物。
        private Pollutants pollutants;
        
        //    显示污染物。
        public void display() {
            System.out.println(new Date().toString());
            System.out.println("PM2.5的指数是" + pollutants.getPM2d5());
            System.out.println("来自 [www.哈哈哈网.com] ");
            System.out.println("========================================================");
        }
    
        public void update(Pollutants pollutants) {
            // TODO Auto-generated method stub
            this.pollutants = pollutants;
            this.display();
        }
    }
    观察者A的代码
    package cn.net.bysoft.observer;
    
    import java.util.Date;
    
    //    空气质量布告板。
    public class AQIDisplay implements Observer, DisplayElement {
        // 污染物。
        private Pollutants pollutants;
    
        public Pollutants getPollutants() {
            return pollutants;
        }
    
        public void setPollutants(Pollutants pollutants) {
            this.pollutants = pollutants;
        }
    
        public void display() {
            System.out.println(new Date().toString());
            System.out.println("经过AQI算法最后得出空气质量为:" + getAQI());
            System.out.println("来自 [www.嘿嘿嘿网.com] 的空气指数数据");
            System.out.println("======================================================");
            System.out.println();
        }
    
        private String getAQI() {
            int num = this.pollutants.getPM2d5();
            String result = "";
            if (num < 25) {
                result = "优";
            } else if (num >= 25 && num < 50) {
                result = "良";
            } else if (num >= 50 && num < 57) {
                result = "轻度污染";
            } else
                result = "重度污染";
            return result;
        }
    
        public void update(Pollutants pollutants) {
            this.pollutants = pollutants;
            this.display();
        }
    }
    观察者B的代码

      编写好这些类与接口后,进行测试,在创建2个类,一个是空气质量监测仪(AirQualityApparatus),一个是空气质量监测服务(AirQualityService,带main函数),代码如下:

    package cn.net.bysoft.observer;
    
    import java.util.Random;
    
    //    空气设备监测仪。
    public class AirQualityApparatus {
        
        public AirQualityApparatus() {}
        
        public AirQualityApparatus(AirQualityStations airQualityStations)
        {
            this.airQualityStations = airQualityStations;
        }
        
        public void Start()
        {
            //    设置空气质量。
            Pollutants pollutants = new Pollutants();
            pollutants.setPM2_5(getRandomNum());
            pollutants.setSO2(getRandomNum());
            pollutants.setNO2(getRandomNum());
            pollutants.setO3(getRandomNum());
            pollutants.setCO(getRandomNum());
            pollutants.setPM10(getRandomNum());
            
            airQualityStations.setPollutants(pollutants);
            airQualityStations.AirQualityChanged();
        }
        
        //    随机生成1-100。
        private int getRandomNum()
        {
            Random r = new Random();
            int num = r.nextInt(100);
            return num + 1;
        }
        
        private AirQualityStations airQualityStations;
    }
    空气设备监测仪代码
    package cn.net.bysoft.observer;
    
    //    空气质量服务中心。
    public class AirQualityService {
    
        public static void main(String[] args) {
            //    建立一个空气质量站。
            AirQualityStations airQualityStations = new AirQualityStations();
            //    建立一个空气质量监测设备。
            AirQualityApparatus airQualityApparatus = new AirQualityApparatus(airQualityStations);
    
            //    添加需要空气质量数据的网站。
            airQualityStations.registerObserver(new PM2d5Display());
            airQualityStations.registerObserver(new AQIDisplay());
            
            while(true)
            {
                airQualityApparatus.Start();
                
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }
    空气质量服务中心代码,main函数类

      运行结果如图:

      Java内置了观察者模式,与上面自定义的观察者模式有一些小差异。主题为java.util.Observable类,观察者为java.util.Observer接口。Observer接口只声明了一个方法,即为void update(Observable o, Object arg)。而Observable类则与上面的Subject有些不同。

      Observable类多出了一个setChanged()方法,用来标记观察的目标状态已经改变,好让notifyObservers()方法知道当它被调用时应该更新观察者。因为有时不需要某种数据只要改变就会发送消息给观察者,比如温度,我们希望温度上升一度在将消息发送给观察者,而不是上升0.1度就发送。具体可以看Observable类的源码:

    package java.util;
    
    public class Observable {
        private boolean changed = false;
        private Vector<Observer> obs;
    
        public Observable() {
            obs = new Vector<>();
        }
    
        public synchronized void addObserver(Observer o) {
            if (o == null)
                throw new NullPointerException();
            if (!obs.contains(o)) {
                obs.addElement(o);
            }
        }
    
        public synchronized void deleteObserver(Observer o) {
            obs.removeElement(o);
        }
    
      
        public void notifyObservers() {
            notifyObservers(null);
        }
    
        public void notifyObservers(Object arg) {
            Object[] arrLocal;
    
            synchronized (this) {
                if (!changed)
                    return;
                arrLocal = obs.toArray();
                clearChanged();
            }
    
            for (int i = arrLocal.length-1; i>=0; i--)
                ((Observer)arrLocal[i]).update(this, arg);
        }
    
        public synchronized void deleteObservers() {
            obs.removeAllElements();
        }
    
        protected synchronized void setChanged() {
            changed = true;
        }
    
        protected synchronized void clearChanged() {
            changed = false;
        }
    
        public synchronized boolean hasChanged() {
            return changed;
        }
    
        public synchronized int countObservers() {
            return obs.size();
        }
    }
    Observable源码

      使用Java内置的观察者修改一下上面的空气质量监测站,代码与测试结果如下:

    package cn.net.bysoft.observer;
    
    import java.util.Observable;
    
    //    空气质量监测站类,继承了java内置的可观察者类。
    public class AirQualityStations extends Observable {
        
        // 空气质量数据更新。
            // 空气质量监测仪会自动调用该方法,并更新pollutants的数据。
            public void AirQualityChanged() {
                super.setChanged();
                //    没有将数据传递给观察者,观察者必须自己到这个类中拉数据。
                super.notifyObservers();
            }
        
        public Pollutants getPollutants() {
            return pollutants;
        }
        
        public void setPollutants(Pollutants pollutants) {
            this.pollutants = pollutants;
        }
    
        // 污染物对象。
        private Pollutants pollutants;
    }
    空气质量监测站的代码
    package cn.net.bysoft.observer;
    
    import java.util.Date;
    import java.util.Observable;
    import java.util.Observer;
    
    //    PM2.5布告板。
    public class PM2d5Display implements Observer, DisplayElement {
        //    污染物。
        private Pollutants pollutants;
        
        //    显示污染物。
        public void display() {
            System.out.println(new Date().toString());
            System.out.println("PM2.5的指数是" + pollutants.getPM2d5());
            System.out.println("来自 [www.哈哈哈网.com] ");
            System.out.println("========================================================");
        }
    
        /*public void update(Pollutants pollutants) {
            // TODO Auto-generated method stub
            this.pollutants = pollutants;
            this.display();
        }*/
        
        //    实现java内置的观察者接口的update方法。
        public void update(Observable o, Object arg) {
            // 获得被观察者类,也就是空气监测站。
            AirQualityStations airQualityStations = (AirQualityStations)o;
            this.pollutants = airQualityStations.getPollutants();
            this.display();
        }
    }
    观察者类的代码
    package cn.net.bysoft.observer;
    
    //    空气质量服务中心。
    public class AirQualityService {
    
        public static void main(String[] args) {
            // 建立一个空气质量站。
            AirQualityStations airQualityStations = new AirQualityStations();
            // 建立一个空气质量监测设备。
            AirQualityApparatus airQualityApparatus = new AirQualityApparatus(
                    airQualityStations);
    
            // 添加需要空气质量数据的网站。
            airQualityStations.addObserver(new PM2d5Display());
            airQualityStations.addObserver(new AQIDisplay());
    
            while (true) {
                airQualityApparatus.Start();
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
    }
    修改后的main方法

      如果你想"推"(push)数据给观察者,你可以把数据当作数据对象传送给notifyObservers(arg)方法,否则,观察者就必须从可观察者对象中"拉"(pull)数据,就像上诉代码中的做法。注意,文字输出的次序与之前的测试也不一样了,因为我们实现的是Observable类的notifyObservers()方法,这导致了通知观察者的次序不同与之前的次序。

      最后,java内置的Observable也有黑暗面,首先它是一个类,而不是接口,并且它也没有去实现任何一个接口。所以我们必须设计一个类去继承它。如果这个类想做被java内置的观察者类的同时又想继承某个父类,就会陷入两难,毕竟Java不支持多继承。这限制了Observable类的复用潜力。再者,因为Observable没有接口,看Observable源码中setChanged()方法被定义为protected。这意味着除非你继承自Observable,否则无法创建Observable实例被组合到自己的对象中来。所以用哪种方式合适需要斟酌,其实自己编写观察者模式也没问题,毕竟它那么简单。

  • 相关阅读:
    hdu 2065
    hdu 1999
    hdu 1562
    hdu 1728
    hdu 1180
    hdu 1088
    hdu 2133
    很好的例子。。
    putty 多标签式浏览
    df
  • 原文地址:https://www.cnblogs.com/DeadGardens/p/5172933.html
Copyright © 2020-2023  润新知