设计模式-观察者模式
前言
整个9月份基本是在花式加班中疲劳度过的,工作中进步不少,自学进度却放慢了。十一长假,处理完家里的事情后提前一天来了上海,收拢一下思绪,准备迎接下一阶段的工作、学习。不知不觉,2019年只剩下了不到三个月,来自时间的压迫感无时无刻不在,需要抓紧最后的机会,利用好这三四个月的时间。
闲话少叙,这一次我打算将观察者设计模式梳理一下,从JDK中的设计,到Spring中的应用,都会涉及到。心得以及感悟都是一家之言,如有不恰当之处,还望各位道友指正!
一、结合案例分析java中的观察者模式
首先看一下java中已经定义好了的观察者类(Observer)、被观察者类(Observable)的结构:
1 // 观察者类 2 public interface Observer { 3 // 此方法用于定义观察者观察到变化后发生的行为 4 // 第一个参数是被观察者;第二个参数是一个可变对象,方便动态传递某些信息 5 void update(Observable o, Object arg); 6 }
1 public class Observable { 2 // 变动标识,用于判断被观察者是否有变化 3 private boolean changed = false; 4 // 存放观察者 5 private Vector<Observer> obs; 6 7 public Observable() { 8 obs = new Vector<>(); 9 } 10 11 public synchronized void addObserver(Observer o) { 12 if (o == null) 13 throw new NullPointerException(); 14 if (!obs.contains(o)) { 15 obs.addElement(o); 16 } 17 } 18 19 public synchronized void deleteObserver(Observer o) { 20 obs.removeElement(o); 21 } 22 23 public void notifyObservers() { 24 notifyObservers(null); 25 } 26 // 通知被观察者发生了变化,循环调用update方法 ☆ 27 public void notifyObservers(Object arg) { 28 Object[] arrLocal; 29 30 synchronized (this) { 31 if (!changed) 32 return; 33 arrLocal = obs.toArray(); 34 clearChanged(); 35 } 36 37 for (int i = arrLocal.length-1; i>=0; i--) 38 ((Observer)arrLocal[i]).update(this, arg); 39 } 40 41 public synchronized void deleteObservers() { 42 obs.removeAllElements(); 43 } 44 // 将改变状态设置成已改变 45 protected synchronized void setChanged() { 46 changed = true; 47 } 48 49 protected synchronized void clearChanged() { 50 changed = false; 51 } 52 53 public synchronized boolean hasChanged() { 54 return changed; 55 } 56 57 public synchronized int countObservers() { 58 return obs.size(); 59 } 60 }
可见被观察者类中维护了一个观察者的列表,在发生变化通知观察者时是循环列表,调用每个观察者的update方法,具体每个观察者是如何做的,取决于其update方法。
知道了java中的观察者和被观察者类,我们要如何使用呢?且看下面的例子。
十一回来的路上看了好几篇半佛仙人的文章,略有感悟,于是此次就用仙人的案例作为主体进行设计。仙人是一个风控出身的"社会工程学专家",热衷于写文章揭露社会上的沙雕事件、违法事件。所以此处观察者就是仙人,而被观察者则是违法事件,下面是定义出来的两个类:
1 /** 2 * 仙人是观察者,实现Observer观察者接口 3 */ 4 public class CelestialBeing implements Observer { 5 6 @Override 7 public void update(Observable o, Object arg) { 8 writeToPen(); 9 } 10 // 写文章揭露(也就是喷) 11 private void writeToPen(){ 12 System.out.println("写文章批斗、吊打"); 13 } 14 }
1 /** 2 * 违法事件是被观察者,一旦出现变化,会被半佛仙人观察到 3 */ 4 public class IllegalThing extends Observable { 5 6 public void change() { 7 System.out.println("出现了违法事件"); 8 super.setChanged(); 9 } 10 }
下面是测试类:
1 public class TestClient { 2 3 public static void main(String[] args) { 4 IllegalThing illegalThing = new IllegalThing(); 5 /** 6 * 观察者模式的关键点:被观察者持有观察者 7 */ 8 illegalThing.addObserver(new CelestialBeing()); 9 // 有改变 10 illegalThing.change(); 11 // 通知所有观察者 12 illegalThing.notifyObservers(); 13 14 } 15 }
运行结果:
1 出现了违法事件 2 写文章批斗、吊打
可以看到,由于被观察者类Observable自身已经维护好了观察者列表,所以我们的被观察者类不需要做太多的事情,只需要将setChanged方法暴露出去即可。观察者模式的关键,就是上面注释中提到的:被观察者持有观察者列表。只要注意了这一点,相信大家在实际场景中使用时便不会用错。
二、看看观察者模式在Spring中的应用
1、在Spring容器的某个阶段触发事件
如果我想在Spring容器refresh之后触发某些特定逻辑,那么定义一个这样的类就可以:
1 @Component 2 public class MySpringListener implements ApplicationListener<ContextRefreshedEvent> { 3 4 @Override 5 public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) { 6 System.out.println("MySpringObserver执行了"); 7 // 执行自定义逻辑 8 } 9 }
能如此方便的做到,主要是因为Spring帮我们创建好了refresh的事件,在Spring容器过程中类似的事件有以下几个:
2、自定义的事件触发
如果我们想在某些特定的时机触发一个自定义的事件,比如在发邮件时触发一个事件监听,那么在Spring中要怎么做呢?
首先定义一个邮件事件:
1 public class MySpringEmailEvent extends ApplicationEvent { 2 3 public MySpringEmailEvent(Object source) { 4 super(source); 5 } 6 }
1 @Component 2 public class MySpringEmailListener implements ApplicationListener<MySpringEmailEvent> { 3 4 @Override 5 public void onApplicationEvent(MySpringEmailEvent mySpringEmailEvent) { 6 System.out.println("MySpringEmailListener执行了"); 7 // 执行自定义逻辑 8 } 9 }
1 @Component 2 public class MyEventTrigger { 3 @Autowired 4 private ApplicationContext applicationContext; 5 // 触发事件 6 public void sendEmail () { 7 applicationContext.publishEvent(new MySpringEmailEvent(applicationContext));
8 }
9 }
测试类:
1 public class SpringClient { 2 public static void main(String[] args) { 3 AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class); 4 MyEventTrigger bean = applicationContext.getBean(MyEventTrigger.class); 5 bean.sendEmail(); 6 } 7 }
执行结果:
1 MySpringEmailListener执行了
看到这里,相信很多道友会有疑问,Spring是如何将事件和listener关联在一起的呢?其实内部是通过ApplicationEventMulticaster接口,它是如何实现的呢?且看下回分析...