设计模式-观察者模式

Posted studymorerich

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了设计模式-观察者模式相关的知识,希望对你有一定的参考价值。

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     @Override
24     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     @Override
37     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     @Override
48     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 }
SpringSubject

 

我们的猫咪观察者出场了!

技术分享图片
 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     @Override
36     public void update(Object data) {
37         System.out.println("小猫咪接受到最新的数据啦");
38         this.data = data;
39     }
40 }
CatObserver

 

到现在为止,我们就构建好了。最好就是测试代码啦

技术分享图片
 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 }
ObserverMain

这段代码显得很丑陋,强制转换用了两次,用 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     @Override
30     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 observer
41      */
42     @Override
43     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     @Override
54     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 }
改进后的 SpringSubject


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 }
Observer
技术分享图片
 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     @Override
34     public void update(Object data) {
35         System.out.println("小猫咪接受到最新的数据啦");
36         this.data = subject.data;
37     }
38 
39     /**
40      * 当观察者自己需要最新的数据时,观察者自己去主题内部取自己需要的数据
41      */
42     @Override
43     public void update(Subject subject) {
44         SpringSubject s = (SpringSubject)subject;
45         this.data = s.getData();
46     }
47     /**
48      * 获取主题
49      * @return
50      *      观察者主题
51      */
52     public Subject getSubject(){
53         return this.subject;
54     }
55 }
CatObserver

 

最后的这个例子的类图如下:

技术分享图片

这个模式我们就说完啦,来总结观察者啦

总结:


观察者模式:定义对象间一对多的依赖关系,当一个对象发生变化时,依赖其的多个对象都会收到通知并自动更新

  观察者模式优点:
        抽象主题只依赖于抽象观察者
        观察者模式使信息产生层和响应层分离

























以上是关于设计模式-观察者模式的主要内容,如果未能解决你的问题,请参考以下文章

Java设计模式补充:回调模式事件监听器模式观察者模式(转)

设计模式观察者模式 ( 简介 | 适用场景 | 优缺点 | 代码示例 )

观察者模式

未调用 LiveData 观察者

设计模式之美(c++)-笔记-56-观察者模式

设计模式之美(c++)-笔记-56-观察者模式