设计模式之观察者模式

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的观察者模式类图:

设计模式之观察者模式