zoukankan      html  css  js  c++  java
  • 设计模式之观察者模式

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

  • 相关阅读:
    Java21-统计字符串中每个字符出现的次数
    算法练习之字符串切割及统计每个字符出现的次数
    Java20-HashMap
    Java19-hashSet
    Java18-泛型的基础知识
    Java17-foreach循环遍历
    Java16-【转载】ArrayList和LinkedList的区别
    Unity查看Editor下PlayerPrefs信息
    Unity下自定义快捷键创建UGUI元素
    Unity中去除贴图中多余的透明区域
  • 原文地址:https://www.cnblogs.com/Scott007/p/3493339.html
Copyright © 2011-2022 走看看