观察者模式java
定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的全部依赖者都会收到通知并自动更新。算法
出版者+订阅者=观察者模式。以报社与订报人为例:编程
举个简单的例子来描述观察者模式。dom
某某市要创建空气质量站,购买了最新型的空气质量监测仪来实时监测城市的空气质量。并开放接口将空气质量提供给全部须要的人或公司。大致结构以下:ide
空气质量站(AirQualityStations)知道如何采集空气质量检测仪监测到的数据,获得数据后发送给PM2.5数据网(PM2d5Display)和空气质量发布网(AQIDisplay)。函数
功能很简单,首先看一段实现的代码(错误的示范):测试
// 空气质量数据更新。 // 空气质量监测仪会自动调用该方法,并更新pollutants的数据。 public void AirQualityChanged() { // 将最新的空气质量数据更新给这两个网站。 pm2d5Display.Update(pollutants); aqiDisplay.Update(pollutants); }
在这个实现中,犯了几个错误:网站
若是使用个观察者模式,将会很好的解决这些问题,观察者模式以松耦合的方式定义了一系列对象之间的一对多关系。当一个对象改变状态,其余依赖者都会收到通知。this
设计原则spa
为了交互对象之间的松耦合设计而努力。
如今,使用观察者模式设计这个需求,类图以下:
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(); } }
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(); } }
编写好这些类与接口后,进行测试,在建立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(); } } } }
运行结果如图:
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(); } }
使用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(); } } } }
若是你想"推"(push)数据给观察者,你能够把数据看成数据对象传送给notifyObservers(arg)方法,不然,观察者就必须从可观察者对象中"拉"(pull)数据,就像上诉代码中的作法。注意,文字输出的次序与以前的测试也不同了,由于咱们实现的是Observable类的notifyObservers()方法,这致使了通知观察者的次序不一样与以前的次序。
最后,java内置的Observable也有黑暗面,首先它是一个类,而不是接口,而且它也没有去实现任何一个接口。因此咱们必须设计一个类去继承它。若是这个类想作被java内置的观察者类的同时又想继承某个父类,就会陷入两难,毕竟Java不支持多继承。这限制了Observable类的复用潜力。再者,由于Observable没有接口,看Observable源码中setChanged()方法被定义为protected。这意味着除非你继承自Observable,不然没法建立Observable实例被组合到本身的对象中来。因此用哪一种方式合适须要斟酌,其实本身编写观察者模式也没问题,毕竟它那么简单。