使用场景 一个对象的属性发生改变时,需要通知到依赖它的对象并自动更新
实现原理 针对使用场景,如何用代码去实现这种效果,只要理解了观察者模式的基本原理,代码一目了然
首先明确有两个对象: 观察者,被观察者,我更喜欢理解为监视器和目标
目标对象里维护一个注册列表,里面记录了注册过的监视器,对外提供注册列表的添加和移除api
目标发生改变时,遍历这个列表里的所有监视器,通过调用监视器的一个方法通知监视器
代码思路 原理并不难,可以说十分简单,两个类,注册列表用一个List集合表示,是否发生改变用一个布尔值表示,很容易手写出来。
首先是目标类,读者根据上述思路阅读代码应该没有什么障碍
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 public class Target { private String name; private List<Monitor> monitors = new ArrayList<>(); private boolean isChanged = false ; public void registMonitor (Monitor monitor) { Objects.requireNonNull(monitor); monitors.add(monitor); } public void deregistMonitor (Monitor monitor) { Objects.requireNonNull(monitor); monitors.remove(monitor); } public void setName (String name) { this .name = name; this .isChanged = true ; this .notifyMonitor(); } public void notifyMonitor () { if (monitors.size() > 0 && this .isChanged){ for (Monitor monitor: monitors){ monitor.update(this ); } this .isChanged = false ; } } public String getName () { return this .name; } }
然后是监视者类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Monitor { private String name; public void update (Target target) { System.out.println("当前监视器为:" + getName() + ",监视的对象已发生改变,目标名称为:" + target.getName()); } public String getName () { return name; } public void setName (String name) { this .name = name; } }
然后通过测试类测试一下1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public class Main { public static void main (String[] args) { Monitor monitor1 = new Monitor(); monitor1.setName("1号" ); Monitor monitor2 = new Monitor(); monitor2.setName("2号" ); Target target = new Target(); target.registMonitor(monitor1); target.registMonitor(monitor2); target.setName("jim" ); target.deregistMonitor(monitor1); target.setName("tom" ); } }
输出结果如下:
1 2 3 当前监视器为:1号,监视的对象已发生改变,目标名称为:jim 当前监视器为:2号,监视的对象已发生改变,目标名称为:jim 当前监视器为:2号,监视的对象已发生改变,目标名称为:tom
说明通知成功,并且注销监视器对象后,不再接收到通知
JDK自带的观察者模式接口 为什么要用JDK自带的Observable(被观察者),Observer(观察者)呢?
点开Observable的源码,since JDK1.0就有的一个类,十分古老,所以出现Vector也不足为奇了,再看里面的方法,关于修改Vector和changed的地方都被synchronized修饰,说明JDK对线程安全性考虑的很好
JDK源码 Observable类中用于通知观察者的源码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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); }
实现流程大致类似
JDK方式代码实现 Step 1: 被观察者 继承Observable
1 2 3 4 5 6 7 8 9 10 11 12 13 public class Target extends Observable { private String name; public void setName (String name) { this .name = name; this .setChanged(); this .notifyObservers("name has been changed, now is " + name); } public String getName () { return name; } }
Step 2: 观察者 1 2 3 4 5 6 public class Watcher implements Observer { @Override public void update (Observable o, Object arg) { Target target = (Target) o; System.out.println("I am be notified by " + target.getName() + ", message is " + arg); }}
Step 3: 验证 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public static void main (String[] args) { Target target = new Target(); Watcher foo = new Watcher(); Watcher bar = new Watcher(); target.addObserver(foo); target.addObserver(bar); target.setName("Jim" ); target.deleteObserver(bar); target.setName("Kim" ); }
控制台输出 第一次setName通知了两个观察者,然后移除了bar观察者,下一次通知就只有一个观察者收到了
1 2 3 I am be notified by Jim, message is name has been changed, now is Jim I am be notified by Jim, message is name has been changed, now is Jim I am be notified by Kim, message is name has been changed, now is Kim
如果对JDK自带的线程安全实现方式不满意,可以自行实现这两个类