• 设计模式-观察者模式


    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

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

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

    总结:


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

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

  • 相关阅读:
    多项输入
    输出多项数据
    圆的面积
    计算并显示
    半径计算圆的周长和面积
    三角形的面积
    显示数据二进制形式
    将一批数据以二进制形式存放在磁盘文件中
    读入字符存入磁盘文件
    数组送到磁盘文件存放
  • 原文地址:https://www.cnblogs.com/studyMoreRich/p/designpattern_observer.html
Copyright © 2020-2023  润新知