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

观察者模式

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

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

  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实例被组合到自己的对象中来。所以用哪种方式合适需要斟酌,其实自己编写观察者模式也没问题,毕竟它那么简单。

原文地址:https://www.cnblogs.com/DeadGardens/p/5172933.html