设计模式之观察者模式
1 概述
观察者模式(Observer Patern),定义了对象间的一对多依赖,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。其目的就是为了对象间的解耦。
这个模式的角色有以下几种:
(1)抽象主题(Subject)角色:它把所有对观察者对象的引用保存在一个集合中,每个主题都可以有多个观察者,抽象主题提供一个接口,可以增加和删除观察者对象;
(2)具体主题(ConcreteSubject)角色:将相关状态存入具体观察者对象,当状态改变时,给所有注册过的观察者发出通知;
(3)抽象观察者(Observer)角色:为所有的具体观察者定义一个接口,在得到主题的通知时更新自己;
(4)具体观察者(ConcreteObserver)角色:实现抽象观察者角色所要求的更新接口,具体观察者角色可以保持一个指向具体主题对象的引用。
2 示例
观察者模式的应用还是挺多的,像我们平常用邮箱查看某个网站的更新信息的RSS订阅就就是这种模式的典型应用,另外熟悉Zookeeper的同志们也知道,ZK中的Watcher机制实际上也就是观察者模式。
就具体实现来说,可以使用JDK自带的Observer类,当然也可以自己搞一套。下面的例子中,我们首先利用自己写的Java类来实现观察者模式,然后再用JDK自带的Observer实现一把。
我们这个例子呢,还是以手机上的应用为例。不管是微信也好,易信也罢,甚至是来往,模式都差不多,上面都提供了朋友圈和一些公众账号。让个人关注了公众账号之后,如果这个公众账号有内容更新,则会推送消息到关注它的客户端上。
首先创建个接口,就是抽象主题:
1 package org.scott.observer; 2 /** 3 * @author Scott 4 * @date 2013年12月26日 5 * @description 6 */ 7 public interface Subject { 8 public abstract void register(Observer observer); 9 public abstract void remove(Observer observer); 10 public abstract void notifyObservers(); 11 }
抽象主题中,有三个方法,register方法就是观察者的注册方法,remove方法是移除一个指定的观察者,notifyObserver方法是当主题发生变化时,通知给已经注册的所有观察者。有了抽象的主题,还得有个抽象的观察者:
1 package org.scott.observer; 2 /** 3 * @author Scott 4 * @date 2013年12月26日 5 * @description 6 */ 7 public interface Observer { 8 public abstract void update(String title, String content); 9 }
update方法,是当主题变化时,就调用所有已注册观察者的这个方法,这个接口必须所有的观察者都实现。下面是个具体的主题:
1 package org.scott.observer; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 /** 7 * @author Scott 8 * @date 2013年12月26日 9 * @description 10 */ 11 public class NewsSubject implements Subject { 12 13 private List<Observer> observerList = null; 14 private String content = null; 15 private String title = null; 16 17 public NewsSubject(){ 18 observerList = new ArrayList<Observer>(); 19 } 20 21 @Override 22 public void register(Observer observer) { 23 observerList.add(observer); 24 } 25 26 @Override 27 public void remove(Observer observer) { 28 int index = observerList.indexOf(observer); 29 if(index >= 0){ 30 observerList.remove(observer); 31 } 32 } 33 34 @Override 35 public void notifyObservers() { 36 if(observerList != null && !observerList.isEmpty()){ 37 for(Observer observer : observerList){ 38 observer.update(title, content); 39 } 40 } 41 } 42 43 public void publishNews(String title, String content){ 44 this.title = title; 45 this.content = content; 46 notifyObservers(); 47 } 48 }
这是我们自定义的主题,新闻的主题。这个主题实现了Subject接口,除了接口中的几个方法之外,还有两个值得注意的地方。
(1)List<Observer>,这是内置的链表,用于保存所有的观察者对象;
(2)publishNews方法,这是更新主题内容的方法,当主题更新内容的时候,调用notify方法,来通知所有的观察者;
有了自定义的主题,下面就是自定义的观察者,用于接受所有的新闻主题变化:
1 package org.scott.observer; 2 /** 3 * @author Scott 4 * @date 2013年12月26日 5 * @description 6 */ 7 public class ScottObserver implements Observer { 8 9 private Subject subject; 10 private String title; 11 private String content; 12 13 public ScottObserver(Subject subject){ 14 this.subject = subject; 15 this.subject.register(this); 16 } 17 18 @Override 19 public void update(String title, String content) { 20 this.content = content; 21 this.title = title; 22 printMsg(); 23 } 24 25 public void printMsg(){ 26 System.out.println("The title of the news is " + this.title 27 + ", the content is " + this.content); 28 } 29 30 }
观察者类中拥有抽象主题的一个对象Subject,用于向所关心的主题订阅和解除订阅,而接口中的update方法,我们这里实现的就是给主题的通知内容赋值。
客户端代码:
1 package org.scott.observer; 2 /** 3 * @author Scott 4 * @date 2013年12月26日 5 * @description 6 */ 7 public class ObserverTest { 8 9 public static void main(String[] args) { 10 NewsSubject subject = new NewsSubject(); 11 Observer observer = new ScottObserver(subject); 12 13 subject.publishNews("电子商务新闻", "京东商城2013年超1000亿,实现微盈利。"); 14 subject.publishNews("时政信息", "安培晋三参拜靖国神社。"); 15 } 16 }
客户端中,我们的Scott观察者,订阅了新闻主题,当有新的新闻时,能够通知到订阅的观察者:
The title of the news is 电子商务新闻, the content is 京东商城2013年超1000亿,实现微盈利。
The title of the news is 时政信息, the content is 安培晋三参拜靖国神社。
--------------------------------------------------------------------------------------------------------------------------------------------------------
上面是我们自己实现的观察者模式,实际上,JDK提供了一个java.util.Observerable类和一个java.util.Observer接口。之前我们的主题是实现了自己定义的org.scott.observer接口,下面要实现的主题是继承java.util.Observerable这个类:
1 package org.scott.observer; 2 3 import java.util.Observable; 4 5 /** 6 * @author Scott 7 * @date 2013年12月26日 8 * @description 9 */ 10 public class NewsSubjectJDK extends Observable { 11 private String content = null; 12 private String title = null; 13 14 public String getContent() { 15 return content; 16 } 17 18 public String getTitle() { 19 return title; 20 } 21 22 public NewsSubjectJDK(){ 23 } 24 25 public void publishNews(String title, String content){ 26 this.title = title; 27 this.content = content; 28 29 //Observable类中的两个方法 30 setChanged(); 31 notifyObservers(); 32 } 33 }
此类中,有两个方法是直接取的个java.util.Observerable类,那就是setChanged方法和notifyObservers方法。setChanged方法是将java.util.Observerable类中的changed标记设置为true,因为只有在changed为true的时候,notifyObservers方法才会通知所有注册的观察者。我们就不用像前面的NewsSubject类中一样需要自己手动实现一个notifyObservers方法,也不需要自己维护一个观察者的List链表。
并且,其中的变量,我们都增加了getter方法,因为前文是使用的“推”模式,就是主题有更新后直接推送到客户端显示出来,而我们即将使用JDK自带的方式实现观察者,是采用“拉”模式,由观察者利用主题的getter方法获取更新的内容。
实现了主题之后,就该观察者了,先看下代码:
1 package org.scott.observer; 2 3 import java.util.Observable; 4 import java.util.Observer; 5 6 /** 7 * @author Scott 8 * @date 2013年12月26日 9 * @description 10 */ 11 public class JDKObserver implements Observer { 12 13 private Observable subject; 14 private String title; 15 private String content; 16 17 public JDKObserver(Observable subject){ 18 this.subject = subject; 19 this.subject.addObserver(this); 20 } 21 22 @Override 23 public void update(Observable obs, Object arg1) { 24 if(obs instanceof NewsSubjectJDK){ 25 NewsSubjectJDK newsSubject = (NewsSubjectJDK) obs; 26 this.content = newsSubject.getContent(); 27 this.title = newsSubject.getTitle(); 28 printMsg(); 29 } 30 } 31 32 public void printMsg(){ 33 System.out.println("The title of the news is " + this.title 34 + ", the content is " + this.content); 35 } 36 }
这里的观察者是实现了java.util.Observer接口,将其中的update方法实现,看到不同了吧?这里的update方法有两个参数,和之前的不同,我们之前自定义的两个参数都是String类型,这里的一个是Observable类型,就是主题的父类,另一个类型Object,属于自定义的。除了参数不同之外,update的内部实现也不同,看到“拉方式”了吧:
this.content = newsSubject.getContent(); this.title = newsSubject.getTitle();
自然,前提是观察者中还是要保存一个主题对象NewsSubjectJDK,至于成员变量java.util.Observerable类,它的目的就是为了方便观察者像主题订阅信息和解除订阅等。当观察者订阅了多个主题时,通过instanceof来判断是哪个主题,不同的主题有不同的处理方式。
测试类:
1 package org.scott.observer; 2 3 import java.util.Observer; 4 5 /** 6 * @author Scott 7 * @date 2013年12月26日 8 * @description 9 */ 10 public class ObserverJDKTest { 11 12 public static void main(String[] args) { 13 NewsSubjectJDK subject = new NewsSubjectJDK(); 14 Observer observer = new JDKObserver(subject); 15 16 subject.publishNews("电子商务新闻", "京东商城2013年超1000亿,实现微盈利。"); 17 subject.publishNews("时政信息", "安培晋三参拜靖国神社。"); 18 } 19 20 }
测试结果:
The title of the news is 电子商务新闻, the content is 京东商城2013年超1000亿,实现微盈利。
The title of the news is 时政信息, the content is 安培晋三参拜靖国神社。
达到了一样的效果。
最后上个Head First的观察者模式类图: