以下为观察者模式详解:
引子:
假设有这样一个问题,有一条河经过一个山谷,山谷下有一个村庄,人们在山谷处修建了一个水库,并安排专人管理,当水库的水位过高时要通知下游居民注意水库的开闸放水,当水库的水温过低时要通知到水库游泳时要注意安全,那么现在我们要用OO思想用代码来设计这样一件事情,该怎么做?
首先我们想像一下我们订阅报纸或杂志的过程,先向报社订阅报纸,订阅后只要这家报社还在运营就会按时把报纸送到你家,当你不想再看这家的报纸时只需要取消订阅,以后就不会再送报纸给你了。
在这样一件事情中读者扮演的是一个观察者的角色,不同的读者同时接收一份报纸查看新闻,他们对新闻的关心点不一。而报社提供新闻的更新,这就是观察者模式,也就是出版者+订阅者。注意:收到报纸的人不一定就只自己看他还可以把报纸发送给其他人,一个人身兼多个角色。
我们可以把出版者叫做主题,读者叫做观察者,这是一种一对多的关系,观察者依赖于此主题,只要主题的状态一有变化,就会通知给观察者,而观察者要想收到通知就要先向主题注册,不想收到时再注销。那么观察者模式是怎么设计的呢?先看这个观察者模式的类图:
先定义两个接口,Subject和Observer,Subject中有3个方法分别为,注册(调用注册方法时把观察者加入到列表中),删除(把观察者从列表中删除)和通知(遍历列表逐个发送更新),Observer接口中有一个更新方法,具体主题类ConcreteSubject实现了Subject接口,具体观察者类ConcreteObserver实现了Observer接口。当观察者通过主题 的registerObserver()方法向主题注册了之后,主题一有更新就会通过notifyObserver()方法来调用观察者的update()方法来把通知发送到位,观察者通过主题的removeObserver()来取消注册,观察者收到变更通知后分析变更的数据,进行相应的处理。
观察者模式的原则---松耦合:主题只知道观察者实现了某个接口(Observer接口),主题不需要知道观察者的具体类是谁做了些什么等。可以随时向主题中注册新的观察者,主题唯一依赖的是一个实现了Observer接口的对象的列表,注册添加的对象都存在这个列表中,在运行中可以用新的观察者取代已有的观察者或删除已有的观察者主题 都不会受到任何影响。不关心观察者的类型,不管加入什么样的观察者主题的代码不需要修改。可以独立地复用主题或观察者(在其他地方使用),改变主题或观察者中的一方,不会影响另一方。
现在我们开始用观察者模式来设计水库问题:
先定义两个接口:
/** * @author Homg */ public interface Subject { public void registerObserver(Observer o); public void removeObserver(Observer o); public void notifyObserver(Subject subject,Object datas); } /** * * @author Homg * */ public interface Observer { public void update(Subject subject, Object datas); }
当水库水位或水温有变量时主题要调用update方法来把数据发送给居民,那么也许你会想用
update(int waterLevel, int waterTemperature)来发送数据,但有没有想过要是以后不仅只通知这两个数据,还有别的,那这个方法的参数不是要长到天上去吗?这不科学,所以我们需要把参数封装起来。再者,如果这个观察者注册了多个主题,那么update时是不是需要辨别一下是哪个主题发来的更新?
封装数据的类:
/** * 封装数据,方便操作 * * @author Homg * */ public class ReserviorData { private int waterLevel; private int waterTemperature; public ReserviorData() { super(); } public ReserviorData(int waterLevel, int waterTemperature) { this.waterLevel = waterLevel; this.waterTemperature = waterTemperature; } public int getWaterLevel() { return waterLevel; } public void setWaterLevel(int waterLevel) { this.waterLevel = waterLevel; } public int getWaterTemperature() { return waterTemperature; } public void setWaterTemperature(int waterTemperature) { this.waterTemperature = waterTemperature; } }
观察者居民A类与居民B类:
/** * * @author Homg * */ public class ResidentA implements Observer { // 水位 private int waterLevel; // 水库管理者类的引用 private ReservoirManager reservoirManager; public ResidentA(ReservoirManager reservoirManager) { this.reservoirManager = reservoirManager; // 注册接收 reservoirManager.registerObserver(this); } // 更新方法,用subject辨别主题 ,更新的数据在datas里,取出数据后调用display显示 @Override public void update(Subject subject, Object datas) { // 先辨别是哪个主题 if (subject instanceof ReservoirManager) { ReserviorData reserviorData = (ReserviorData) datas; this.waterLevel = reserviorData.getWaterLevel(); desplay(); } } // 这是改为“拉”数据时要改变的方法 // @Override // public void update(Subject subject, Object datas) { // if (subject instanceof ReservoirManager) { // this.waterLevel = ((ReservoirManager) subject).getWaterLevel(); // desplay(); // } // } // 显示数据 public void desplay() { System.out.println("我是居民A,水库的当前水位为:" + waterLevel + "米。"); } } /** * * @author Homg * */ public class ResidentB implements Observer { private int waterTemperature; private ReservoirManager reservoirManager; public ResidentB(ReservoirManager reservoirManager) { this.reservoirManager = reservoirManager; // 注册接收 reservoirManager.registerObserver(this); } @Override public void update(Subject subject, Object datas) { //先辨别是哪个主题 if (subject instanceof ReservoirManager) { ReserviorData reserviorData = (ReserviorData) datas; this.waterTemperature = reserviorData.getWaterTemperature(); desplay(); } } // 这是改为“拉”数据时要改变的方法 // @Override // public void update(Subject subject, Object datas) { // if (subject instanceof ReservoirManager) { // this.waterTemperature = ((ReservoirManager) // subject).getWaterTemperature(); // desplay(); // } // } public void desplay() { System.out.println("我是居民B,水库的当前水温为:" + waterTemperature + "度。"); } }
再考虑一个问题,我们是否需要一个变量来控制是否发送通知?比如:我们不希望水温或水位有一点点变化就马上通知,这样的话会通知不段居民哪有多么多时间来处理这样琐碎的通知,还有将来可能要在多个地方进行判断是否发送更新。综上所述控制肯定需要,那么我们用一个boolean值来控制,并用set,cancel方法来改变其值,看下面的水库管理者主题类:
/** * * @author Homg * */ public class ReservoirManager implements Subject { // 存放注册者的list private ArrayList<Observer> observerList; // 数据类的引用 private ReserviorData reserviorData; // 用来控制当水库信息数据发生变化时是否发送通知 private boolean isChange = false; public ReservoirManager() { observerList = new ArrayList<Observer>(); reserviorData = new ReserviorData(); } // 用来注册观察者的方法 @Override public void registerObserver(Observer o) { observerList.add(o); } // 用来注销观察者的方法 @Override public void removeObserver(Observer o) { int i = observerList.indexOf(o); if (i >= 0) { observerList.remove(i); } } // 该方法用来通知每一个注册的观察者 @Override public void notifyObserver(Subject subject, Object datas) { if (isChange) { // 遍历观察者列表 for (int i = 0; i < observerList.size(); i++) { Observer observer = observerList.get(i); // 使观察者得到更新的数据 observer.update(subject, datas); } } } // 设置水库信息数据 public void setReservoirData(ReserviorData reserviorData) { this.reserviorData = reserviorData; measurementsChanged(); } // 测量数据的更改 public void measurementsChanged() { // 如果水位高于5或水温低于5度那么就发送通知 if (reserviorData.getWaterLevel() > 5 || reserviorData.getWaterTemperature() < 5) { setChanged(); } notifyObserver(this, reserviorData); // 这是要改成“拉”数据时要改变的代码 // notifyObserver(this, null); } public void setChanged() { isChange = true; } public void cancelChanged() { isChange = false; } // 这是要改成“拉”数据时要添加的方法 // public int getWaterLevel() { // return reserviorData.getWaterLevel(); // } // // public int getWaterTemperature() { // return reserviorData.getWaterTemperature(); // } }
测试条件:
每隔一秒随机改变一次水位和水温值,并显示出时间(秒),代码如下:
/** * @author Homg */ public class Test { /** * @param args */ public static void main(String[] args) { Timer timer = new Timer(); timer.schedule(task, 0, 1000); } private static TimerTask task = new TimerTask() { int time = 0; @Override public void run() { System.out.println("时间为=" + time + "秒"); time++; Random random = new Random(); int waterLevel = random.nextInt(15); int waterTemp = random.nextInt(10); ReserviorData reserviorData = new ReserviorData(waterLevel, waterTemp); setDatas(reserviorData); } }; public static void setDatas(ReserviorData reserviorData) { ReservoirManager reservoirManager = new ReservoirManager(); ResidentA residentA = new ResidentA(reservoirManager); ResidentB residentB = new ResidentB(reservoirManager); reservoirManager.setReservoirData(reserviorData); } }
运行结果:
时间为=0秒
我是居民A,水库的当前水位为:8米。
我是居民B,水库的当前水温为:2度。
时间为=1秒
我是居民A,水库的当前水位为:3米。
我是居民B,水库的当前水温为:1度。
时间为=2秒
我是居民A,水库的当前水位为:1米。
我是居民B,水库的当前水温为:2度。
时间为=3秒
时间为=4秒
我是居民A,水库的当前水位为:12米。
我是居民B,水库的当前水温为:2度。
时间为=5秒
时间为=6秒
我是居民A,水库的当前水位为:12米。
我是居民B,水库的当前水温为:9度。
时间为=7秒
时间为=8秒
我是居民A,水库的当前水位为:7米。
我是居民B,水库的当前水温为:5度。
时间为=9秒
我是居民A,水库的当前水位为:14米。
我是居民B,水库的当前水温为:0度。
时间为=10秒
细看可以发现每秒的水位和水温都不相同,且居民都收到了通知,第3秒,第5秒,第7秒,第10秒时并没有发送通知,这是因为不符合水位高于5或水温低于5度的发送条件所以没有发送通知给居民。
也许你已经发现了这样一个问题:我们上面发送通知是主题“推”送给观察者的,也就是说以后不管加入了多少发送的数据,每个观察者都会收到所有的数据,且不说安全性,如果某个观察者他只想知道水位的变化,那他也要收到这一大堆的数据,这样不科学,所以我们可以把观察者模式改成“拉”数据的形式,再看上面各类中的代码,里面有改成拉数据的代码(注释的方法或变量,代码上面写明了替换或添加)。由此我们也发现了观察者模式的实现方法不只一种。
类图:
应用:
Java api中有提供观察者模式,我们可以去继承和实现,java.util.Observable(主题),java.util.Observer(观察者),注意Observable不是接口是一个类,所以要去继承它,这也导致了不方便,java是单继承的,所以你的类继承了它就不能再继承别的类了,也限制了它的复用性等。
Java中Button的监听,是不是观察者模式?
Android中的广播是不是呢?
总结:
观察者模式就是定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
完整代码下载地址(也可以留下邮箱发给你):