设计模式学习-观察者模式
1.定义
定义对象间一种一对多的依赖关系,使得当每一个对象改变状态,则所有依赖于它的对象都会得到通知并自动更新。
2.类图
3.代码示例
我们定义一个场景:热水壶在烧开水,小孩和妈妈都关注烧开水的过程,各自有其处理方法。用while死循环一直轮询虽然可以实现这样的场景,但性能上让人无法接受。
为方便大家copy源码放在本机测试,我将代码写进一个java类中,其它参与类都是非public的。
1 package com.zhaoyangwoo.observer; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 /** 7 * Created by john on 16/8/3. 8 */ 9 //定义被观察者接口 10 interface Subject { 11 12 void attachObv(Observer o); 13 14 void detachObv(Observer o); 15 16 void notifyObvs(); 17 } 18 19 //定义观察者接口 20 interface Observer { 21 void update(Subject subject); 22 } 23 24 //定义具体被观察者 25 public class WaterHeater implements Subject { 26 27 //存储观察者 28 private static List<Observer> observerList = new ArrayList<>(); 29 30 public int getTemperature() { 31 return temperature; 32 } 33 34 public void setTemperature(int temperature) { 35 this.temperature = temperature; 36 } 37 38 private int temperature = 0; 39 40 @Override 41 public void attachObv(Observer o) { 42 observerList.add(o); 43 } 44 45 @Override 46 public void detachObv(Observer o) { 47 observerList.remove(o); 48 49 } 50 51 @Override 52 public void notifyObvs() { 53 observerList.forEach(r -> r.update(this)); 54 } 55 56 //模拟烧开水的过程 57 public void heat() { 58 temperature = 0; 59 for (int i = 0; i < 100; i++) { 60 temperature++; 61 this.notifyObvs(); 62 try { 63 //停100ms 64 Thread.sleep(100); 65 } catch (InterruptedException e) { 66 e.printStackTrace(); 67 } 68 } 69 } 70 71 72 //场景类 73 public static void main(String[] args) { 74 WaterHeater waterHeater = new WaterHeater(); 75 Mother mother = new Mother(); 76 Child child = new Child(); 77 waterHeater.attachObv(mother); 78 waterHeater.attachObv(child); 79 waterHeater.heat(); 80 waterHeater.detachObv(child); 81 waterHeater.heat(); 82 System.out.println("烧水结束"); 83 } 84 } 85 86 //具体观察者 87 class Child implements Observer { 88 89 @Override 90 public void update(Subject subject) { 91 WaterHeater waterHeater = (WaterHeater) subject; 92 System.out.println("现在的水温是" + waterHeater.getTemperature() + "度,人家还是个宝宝,跟我没关系"); 93 } 94 } 95 96 //具体观察者 97 class Mother implements Observer { 98 99 @Override 100 public void update(Subject subject) { 101 WaterHeater waterHeater = (WaterHeater) subject; 102 if (waterHeater.getTemperature() > 99) { 103 System.out.println("现在的水温是" + waterHeater.getTemperature() + "度,水烧开了,我要把水装进热水瓶"); 104 } 105 } 106 }
4.应用场景举例
- 一个类的状态变化需要通知其他类知晓,并且这些其他类是可以动态配置的
- 可以实现订阅/发布模型
5.JDK源码中的模式实现
JDK原生对观察者模式支持。通过java.util.Observable和java.util.Observer两个类,很容易实现观察者模式。通过阅读其源码,可以发现原理都是一样的。当然源码里的实现是线程安全的。我们用这两个类重写我们的场景:
1 // jdk版本的被观察者,不需要自己实现调用通知/注册之类的操作 2 class WaterHeaterJava extends Observable { 3 public int getTemperature() { 4 return temperature; 5 } 6 7 public void setTemperature(int temperature) { 8 this.temperature = temperature; 9 } 10 11 private int temperature = 0; 12 13 public void heat() { 14 temperature = 0; 15 for (int i = 0; i < 100; i++) { 16 temperature++; 17 //这里一定要注意,如果要notifyObservers生效,一定要调用setChanged告知已经发生了change,可以通知观察者了.否则notifyObservers不工作 18 super.setChanged(); 19 super.notifyObservers(); 20 try { 21 Thread.sleep(100); 22 } catch (InterruptedException e) { 23 e.printStackTrace(); 24 } 25 } 26 } 27 28 //场景类 29 public static void main(String[] args) { 30 WaterHeaterJava waterHeater = new WaterHeaterJava(); 31 MotherJava mother = new MotherJava(); 32 ChildJava child = new ChildJava(); 33 waterHeater.addObserver(mother); 34 waterHeater.addObserver(child); 35 waterHeater.heat(); 36 waterHeater.deleteObserver(child); 37 waterHeater.heat(); 38 System.out.println("Java版烧水结束"); 39 } 40 } 41 42 class ChildJava implements java.util.Observer { 43 44 @Override 45 public void update(Observable o, Object arg) { 46 WaterHeaterJava waterHeater = (WaterHeaterJava) o; 47 System.out.println("现在的水温是" + waterHeater.getTemperature() + "度,人家还是个宝宝,跟我没关系"); 48 } 49 } 50 51 class MotherJava implements java.util.Observer { 52 53 @Override 54 public void update(Observable o, Object arg) { 55 WaterHeaterJava waterHeater = (WaterHeaterJava) o; 56 if (waterHeater.getTemperature() > 99) { 57 System.out.println("现在的水温是" + waterHeater.getTemperature() + "度,水烧开了,我要把水装进热水瓶"); 58 } 59 } 60 }
关于setChanged,看看它的源码就明白了
1 public void notifyObservers(Object arg) { 2 /* 3 * a temporary array buffer, used as a snapshot of the state of 4 * current Observers. 5 */ 6 Object[] arrLocal; 7 8 synchronized (this) { 9 /* We don't want the Observer doing callbacks into 10 * arbitrary code while holding its own Monitor. 11 * The code where we extract each Observable from 12 * the Vector and store the state of the Observer 13 * needs synchronization, but notifying observers 14 * does not (should not). The worst result of any 15 * potential race-condition here is that: 16 * 1) a newly-added Observer will miss a 17 * notification in progress 18 * 2) a recently unregistered Observer will be 19 * wrongly notified when it doesn't care 20 */ 21 22 //看这里 23 if (!changed) 24 return; 25 arrLocal = obs.toArray(); 26 clearChanged(); 27 } 28 29 for (int i = arrLocal.length-1; i>=0; i--) 30 ((Observer)arrLocal[i]).update(this, arg); 31 }
6.思考
思考如下两个问题
- 性能问题
如果注册的观察者较多,或者观察者处理能力弱、耗时长,那么很可能出现性能问题,毕竟只是简单的遍历调用。
- 订阅/发布
当然,对于订阅/发布模型的支持有更好的开源框架,各种mq实现了这种模型,并且是异步架构,性能也有保障。
- 推或拉
大家都喜欢说推模式和拉模式。其实本质上来说都是推模式,这也是使用观察者模式带来的好处。拉模式只不过推了一个引用,可以通过这个引用拿到更多的信息而已。
7.参考
1.《JAVA与模式》之观察者模式