2017-10-04 10:26:11
当我们在 github 上看到一个很好的项目时,也许会想要持续地跟进这个项目的最新进度。当你按下 Watch 按钮时,每当项目更新,那么 github 就会不断地将这个项目的最新进展通过邮件发送给你,提醒你。这就是我们今天谈论的 观察者模式。
请记住:任何模式的具体实现并不是一成不变的,根据具体的情况去改变
github 中有数据是我们所关心的,我们把 github 当作一个大主题,而我们则是观察者。
这个例子的 主题-观察者类图:
我们并不知道会有多少不同的主题会被别人关注,因此最好遵循 面向接口编程,不面向实现编程 这个原则
1 //主题 2 public interface Subject { 3 4 /** 5 * 注册成为观察者 6 * @param observer 观察者对象 7 */ 8 public void regitster(Observer observer); 9 10 /**11 * 移除观察者12 * @param observer 观察者对象13 */14 public void remove(Observer observer);15 16 /**17 * 将最新消息通知观察者18 */19 public void notifyObservers();20 }
上面这个接口是主题接口(也就是被观察者),很明显主题必须有添加、删除观察者以及将最新消息通知观察者等三个方法。Observer 是观察者接口,主题要想添加或删除观察者,就要有观察者对象作为参数。
接下来的代码是观察者接口:
1 //观察者2 public interface Observer {3 /**4 * 当主题通知时,观察者接收最新的数据以用来自己更新5 * @param data 最新的数据6 */7 public void update(Object data);8 }
然后是具体的主题和观察者啦现在在 javaWeb 中最有名的框架无疑就是 Spring 啦,我们就以 SpringSubject 作为具体主题的例子
1 //Spring 主题:SpringSubject 2 public class SpringSubject implements Subject { 3 4 /** 5 * 观察者列表 6 */ 7 private List<Observer> observers; 8 9 /**10 * 主题持有的所有数据11 */12 private String data;13 14 public SpringSubject(){15 observers = new ArrayList<Observer>();16 }17 18 /**19 * 注册成为观察者20 *21 * @param observer 注册的观察者对象22 */23 @Override24 public void regitster(Observer observer) {25 if(!observers.contains(observer)) {26 observers.add(observer);27 System.out.println(observer + "成为了 Spring 的观察者");28 }29 }30 31 /**32 * 移除观察者33 *34 * @param observer 需要移除的观察者35 */36 @Override37 public void remove(Observer observer) {38 if(observers.contains(observer)){39 observers.remove(observer);40 System.out.println(observer + " 不再是 Spring 的观察者啦!");41 }42 }43 44 /**45 * 将最新消息通知观察者46 */47 @Override48 public void notifyObservers() {49 System.out.println("哇!有最新消息啦,赶快通知所有的观察者");50 for (Observer o : observers) {51 o.update(data);52 }53 }54 55 /**56 * 当数据改变时57 */58 public void setChanged(){59 notifyObservers();60 } 61 }
我们的猫咪观察者出场了!
1 //一个好奇的观察者 2 //这里有一只小猫咪对 Spring 很感兴趣,它于是成为了 Spring 的观察者! 3 public class CatObserver implements Observer { 4 5 /** 6 * 观察者所持有的数据 7 */ 8 private Object data; 9 10 /**11 * 这里加上一个主题的域,如果有读者认为不需要的话,也可以自己实现一个12 * 这个主题域,可以更方便的增加和删除观察者13 */14 private Subject subject;15 16 public CatObserver(Subject subject){17 this.subject = subject;18 //猫咪如愿以偿的成为 spring 的粉丝啦19 this.subject.regitster(this);20 }21 22 /**23 * 小猫咪不想再当 spring 的观察者了!一点也不好玩24 */25 public void remove(){26 this.subject.remove(this);27 this.subject = null;28 }29 30 /**31 * 当主题通知时,观察者接收最新的数据以用来自己更新32 *33 * @param data 最新的数据34 */35 @Override36 public void update(Object data) {37 System.out.println("小猫咪接受到最新的数据啦");38 this.data = data;39 }40 }
到现在为止,我们就构建好了。最好就是测试代码啦
1 //这是测试代码 2 public class ObserverMain { 3 4 public static void main(String[] args) { 5 //Spring 主题诞生了! 6 Subject subject = new SpringSubject(); 7 //然后我们的猫咪,它一出来就迫不及待地成为 spring 主题的粉丝 8 Observer cat = new CatObserver(subject); 9 //数据改变10 ((SpringSubject) subject).setChanged();11 //小猫咪退出了12 ((CatObserver) cat).remove();13 }14 }
这段代码显得很丑陋,强制转换用了两次,用 jdk5 出现的泛型修改下代码便可
测试结果如下:
控制通知的频率
听小猫咪说,它之所以退出,是因为 Spring 主题更新太频繁了因此,我们现在修改下这个程序,Spring 主题每次更新一个大版本时在通知消息,小版本不通知
1 //下面是改进的版本:SpringSubject 2 【代码】 3 public class SpringSubject implements Subject { 4 5 /** 6 * 观察者列表 7 */ 8 private List<Observer> observers; 9 10 /**11 * 主题所持有的所有数据12 */13 private String data;14 15 /**16 * 是否通知17 */18 private boolean isNotified = false;19 20 public SpringSubject(){21 observers = new ArrayList<Observer>();22 }23 24 /**25 * 注册成为观察者26 *27 * @param observer 观察者对象28 */29 @Override30 public void regitster(Observer observer) {31 if(!observers.contains(observer)) {32 observers.add(observer);33 System.out.println(observer + "成为了 Spring 的观察者");34 }35 }36 37 /**38 * 移除观察者39 *40 * @param observer41 */42 @Override43 public void remove(Observer observer) {44 if(observers.contains(observer)){45 observers.remove(observer);46 System.out.println(observer + " 不再是 Spring 的观察者啦!");47 }48 }49 50 /**51 * 将最新消息通知观察者52 */53 @Override54 public void notifyObservers() {55 System.out.println("哇!有最新消息啦,赶快通知所有的观察者");56 for (Observer o : observers) {57 o.update(data);58 }59 }60 61 /**62 * 在这里我们就可以控制更新的频率啦63 */64 public void setNotified(){65 isNotified = true;66 }67 68 /**69 * 当数据改变时70 */71 public void setChanged(){72 setNotified();73 if(isNotified) {74 notifyObservers();75 }76 isNotified = false;77 }78 }
isNotified 这个域就是为了通知不那么频繁而设立的,实际情况根据需要使用
推拉其实不一定要我们把数据 推送给 观察者,也可以观察者自己需要数据时,自己来拿,也很好的这就是观察者模式的 推 和 拉 两种方式啦推:主题会把自己所有的数据推送给任何一个观察者,不管这个观察者需不需要所有的数据拉:每当观察者需要最新数据时,观察者都会自己去对应的主题拉走自己需要的数据
观察者模式的 拉 这个方式,由于观察者是主动的,所以 update 这个方法应该有主题的这个参数,下面重写 Observer 和 CatObserver:
1 //观察者 2 public interface Observer { 3 /** 4 * 当主题通知时,观察者接收最新的数据以用来自己更新 5 * 6 * @param data 最新的数据 7 */ 8 public void update(Object data); 9 10 /**11 * 当观察者自己需要最新的数据时,观察者自己去主题内部取自己需要的数据12 * @param subject 观察者依赖的数据的对应主题13 */14 public void update(Subject subject);15 }
1 public class CatObserver implements Observer { 2 3 /** 4 * 观察者所持有的数据 5 */ 6 private Object data; 7 8 /** 9 * 这里加上一个主题的域,如果有读者认为不需要的话,也可以自己实现一个10 * 这里有主题的域,可以更方便的增加和删除观察者11 */12 private Subject subject;13 14 public CatObserver(Subject subject){15 this.subject = subject;16 //猫咪如愿以偿的成为 spring 的粉丝啦17 this.subject.regitster(this);18 }19 20 /**21 * 小猫咪不想再当 spring 的观察者了!一点也不好玩22 */23 public void remove(){24 this.subject.remove(this);25 this.subject = null;26 }27 28 /**29 * 当主题通知时,观察者接收最新的数据以用来自己更新30 *31 * @param data 最新的数据32 */33 @Override34 public void update(Object data) {35 System.out.println("小猫咪接受到最新的数据啦");36 this.data = subject.data;37 }38 39 /**40 * 当观察者自己需要最新的数据时,观察者自己去主题内部取自己需要的数据41 */42 @Override43 public void update(Subject subject) {44 SpringSubject s = (SpringSubject)subject;45 this.data = s.getData();46 }47 /**48 * 获取主题49 * @return50 * 观察者主题51 */52 public Subject getSubject(){53 return this.subject;54 }55 }
最后的这个例子的类图如下:
这个模式我们就说完啦,来总结观察者啦
总结:
观察者模式:定义对象间一对多的依赖关系,当一个对象发生变化时,依赖其的多个对象都会收到通知并自动更新 观察者模式优点: 抽象主题只依赖于抽象观察者 观察者模式使信息产生层和响应层分离
本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728