设计模式解密(16)- 观察者模式

Posted 与其临渊羡鱼,不如退而结网

tags:

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

1、简介

定义:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

主要解决:当一个对象(目标对象)的状态发生改变,如何让所有的依赖对象(观察者对象)都将得到通知。

本质:触发联动

英文:Observer

类型:行为型

2、类图及组成

(引)类图:

组成:

  抽象主题(Subject)角色: 抽象主题角色把所有对观察者对象的引用保存在一个聚集(比如ArrayList对象)里,每个主题都可以有任何数量的观察者。抽象主题提供接口,可以增加和删除观察者对象。抽象主题角色又叫做抽象被观察者(Observable)角色。

  具体主题(ConcreteSubject)角色: 将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色又叫做具体被观察者(Concrete Observable)角色。

  抽象观察者(Observer)角色: 为所有的具体观察者定义一个更新接口,在得到主题的通知时更新自己。

  具体观察者(ConcreteObserver)角色: 观察者的具体实现对象,实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。如果需要,具体观察者角色可以保持一个指向具体主题对象的引用。

3、推模型和拉模型

推模型和拉模型的类图是一样的;

● 推模型

  主题对象向观察者推送主题的详细信息,不管观察者是否需要,推送的信息通常是主题对象的全部或部分数据。

● 拉模型

  主题对象在通知观察者的时候,只传递少量信息。如果观察者需要更具体的信息,由观察者主动到主题对象中获取,相当于是观察者从主题对象中拉数据。一般这种 模型的实现中,会把主题对象自身通过update()方法传递给观察者,这样在观察者需要获取数据的时候,就可以通过这个引用来获取了。

推模式代码结构:

/**
* 目标对象,它知道观察它的观察者,并提供注册和删除观察者的接口
*/
class Subject {
  /**
   * 用来保存注册的观察者对象
   */
  private List<Observer> observers = new ArrayList<Observer>();
  /**
   * 注册观察者对象
   * @param observer 观察者对象
   */
  public void attach(Observer observer) {
     observers.add(observer);
  }
  /**
   * 删除观察者对象
   * @param observer 观察者对象
   */
  public void detach(Observer observer) {
     observers.remove(observer);
  }
  /**
   * 通知所有注册的观察者对象
   */
  protected void notifyObservers(String newState) {
     for(Observer observer : observers){
         observer.update(newState);
     }
  }
}

/**
* 具体的目标对象,负责把有关状态存入到相应的观察者对象,
* 并在自己状态发生改变时,通知各个观察者
*/
class ConcreteSubject extends Subject {
  /**
   * 示意,目标对象的状态
   */
  private String subjectState;
  
  public String getSubjectState() {
     return subjectState;
  }
  
  public void change(String subjectState) {
     this.subjectState = subjectState;
     //状态发生改变,通知各个观察者
     this.notifyObservers(subjectState);
  }
}

/**
* 观察者接口,定义一个更新的接口给那些在目标发生改变的时候被通知的对象
*/
interface Observer {
  /**
   * 更新的接口
   * @param subject 传入目标对象,好获取相应的目标对象的状态
   */
  public void update(String newState);
}

/**
* 具体观察者对象,实现更新的方法,使自身的状态和目标的状态保持一致
*/
class ConcreteObserver implements Observer {
  /**
   * 示意,观者者的状态
   */
  private String observerState;

  @Override
  public void update(String newState) {
     //具体的更新实现
     //这里可能需要更新观察者的状态,使其与目标的状态保持一致
     observerState = newState;
  }
}

/**
 * 客户端调用
 */
public class Client {
    public static void main(String[] args) {
        //创建主题对象
        ConcreteSubject subject = new ConcreteSubject();
        //创建观察者对象
        Observer observer = new ConcreteObserver();
        //将观察者对象登记到主题对象上
        subject.attach(observer);
        //改变主题对象的状态
        subject.change("new state");
    }
}

拉模式代码结构:

/**
* 目标对象,它知道观察它的观察者,并提供注册和删除观察者的接口
*/
class Subject {
  /**
   * 用来保存注册的观察者对象
   */
  private List<Observer> observers = new ArrayList<Observer>();
  /**
   * 注册观察者对象
   * @param observer 观察者对象
   */
  public void attach(Observer observer) {
     observers.add(observer);
  }
  /**
   * 删除观察者对象
   * @param observer 观察者对象
   */
  public void detach(Observer observer) {
     observers.remove(observer);
  }
  /**
   * 通知所有注册的观察者对象
   */
  public void notifyObservers(){
     for(Observer observer : observers){
         observer.update(this);
     }
  }
}

/**
* 具体的目标对象,负责把有关状态存入到相应的观察者对象,
* 并在自己状态发生改变时,通知各个观察者
*/
class ConcreteSubject extends Subject {
  /**
   * 示意,目标对象的状态
   */
  private String subjectState;
  
  public String getSubjectState() {
     return subjectState;
  }
  
  public void change(String subjectState) {
     this.subjectState = subjectState;
     //状态发生改变,通知各个观察者
     this.notifyObservers();
  }
}

/**
* 观察者接口,定义一个更新的接口给那些在目标发生改变的时候被通知的对象
*/
interface Observer {
  /**
   * 更新的接口
   * @param subject 传入目标对象,好获取相应的目标对象的状态
   */
  public void update(Subject subject);
}

/**
* 具体观察者对象,实现更新的方法,使自身的状态和目标的状态保持一致
*/
class ConcreteObserver implements Observer {
  /**
   * 示意,观者者的状态
   */
  private String observerState;

  @Override
  public void update(Subject subject) {
     //具体的更新实现
     //这里可能需要更新观察者的状态,使其与目标的状态保持一致
     observerState = ((ConcreteSubject)subject).getSubjectState();
  }
}

/**
 * 客户端调用
 */
public class Client {
    public static void main(String[] args) {
        //创建主题对象
        ConcreteSubject subject = new ConcreteSubject();
        //创建观察者对象
        Observer observer = new ConcreteObserver();
        //将观察者对象登记到主题对象上
        subject.attach(observer);
        //改变主题对象的状态
        subject.change("new state");
    }
}

两种模型的比较 :

推模型是假定主题对象知道观察者需要的数据;而拉模型是主题对象不知道观察者具体需要什么数据,没有办法的情况下,干脆把自身传递给观察者,让观察者自己去按需要取值。

由此可见:拉模式的适用范围更广;

4、实例解析

这里模拟用户订阅微信公众号的情景(推模式实现)

package com.designpattern.Observer;

import java.util.ArrayList;
import java.util.List;

/**
 * 目标对象,它知道观察它的观察者,并提供注册和删除观察者的接口
 * 这里模拟 -- 微信公众号
 * @author Json<<json1990@foxmail.com>>
 */
public class WechatSubject {
    /**
     * 用来保存注册的观察者对象
     */
    private List<FansObserver> observers = new ArrayList<FansObserver>();
    /**
     * 注册观察者对象
     * @param observer 观察者对象
     */
    public void attach(FansObserver observer) {
       observers.add(observer);
    }
    /**
     * 删除观察者对象
     * @param observer 观察者对象
     */
    public void detach(FansObserver observer) {
       observers.remove(observer);
    }
    /**
     * 通知所有注册的观察者对象
     */
    protected void notify(String article) {
       for(FansObserver observer : observers){
           observer.update(article);
       }
    }
}
package com.designpattern.Observer;

/**
 * 具体的目标对象,负责把有关状态存入到相应的观察者对象,
 * 并在自己状态发生改变时,通知各个观察者
 * 这里模拟 -- 文章类
 * @author Json<<json1990@foxmail.com>>
 */
public class ConcreteWechatSubject extends WechatSubject {
    /**
     * 文章信息
     */
    private String article;
    
    public String getArticle() {
       return article;
    }
    
    public void setArticle(String article) {
       this.article = article;
       System.out.println("发布文章:"+article+"");
       //通知所有粉丝查看文章
       this.notify(article);
    }
}
package com.designpattern.Observer;

/**
 * 观察者接口,定义一个更新的接口给那些在目标发生改变的时候被通知的对象
 * 这里模拟 -- 粉丝用户接口
 * @author Json<<json1990@foxmail.com>>
 */
public interface FansObserver {
    /**
     * 粉丝接收文章接口
     */
    public void update(String article);
}
package com.designpattern.Observer;

/**
 * 具体观察者对象,实现更新的方法,使自身的状态和目标的状态保持一致
 * 这里模拟 -- 具体粉丝用户对象
 * @author Json<<json1990@foxmail.com>>
 */
public class ConcreteFansObserver implements FansObserver {
    /**
     * 文章信息
     */
    private String article;

    @Override
    public void update(String article) {
        this.article = article;
        System.out.println("你关注的微信公众号发布文章了!文章标题:"+article+"");
    }
}

测试:

package com.designpattern.Observer;

/**
 * 测试
 * @author Json<<json1990@foxmail.com>>
 */
public class Client {
    public static void main(String[] args) {
        //创建微信公众号主题
        ConcreteWechatSubject sub = new ConcreteWechatSubject();
        //创建粉丝观察者
        FansObserver obsever = new ConcreteFansObserver();
        FansObserver obsever1 = new ConcreteFansObserver();
        //添加观察者
        sub.attach(obsever);
        sub.attach(obsever1);
        //微信公众号发布文章
        sub.setArticle("《关于建军90周年》");
    }
}

结果:

发布文章:《关于建军90周年》
你关注的微信公众号发布文章了!文章标题:《关于建军90周年》
你关注的微信公众号发布文章了!文章标题:《关于建军90周年》

5、优缺点

优点:

  1、(观察者和被观察者是抽象耦合的)原本被观察者对象在状态改变的时候,需要直接调用所有的观察者对象,但是抽象出观察者接口过后,被观察者和观察者就只是在抽象层面上耦合了,也就是说被观察者只是知道观察者接口,并不知道具体的观察者的类,从而实现被观察者类和具体的观察者类之间解耦。

  2、(实现了动态联动)联动就是做一个操作会引起其它相关的操作。由于观察者模式对观察者注册实行管理,那就可以在运行期间,通过动态的控制注册的观察者,来控制某个动作的联动范围,从而实现动态联动。

  3、(支持广播通信)由于被观察者发送通知给观察者是面向所有注册的观察者,所以每次被观察者通知的信息就要对所有注册的观察者进行广播。当然,也可以通过在被观察者上添加新的功能来限制广播的范围。

缺点:

  1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。

  2、如果在观察者和被观察者之间有循环依赖的话,被观察者会触发它们形成循环调用,可能导致系统崩溃。

  3、观察者模式没有相应的机制让观察者知道被观察者对象是怎么发生变化的,而仅仅只是知道被观察者发生了变化。

6、应用场景

  1、当一个抽象模型有两个方面,其中一个方面的操作依赖于另一个方面的状态变化,那么就可以选用观察者模式,将这两者封装成观察者和被观察者,当被观察者对象变化的时候,依赖于它的观察者对象也会发生相应的变化。这样就把抽象模型的这两个方面分离开了,使得它们可以独立的改变和复用。

  2、当更改一个对象的时候,需要同时连带改变其它的对象,而且不知道究竟应该有多少对象需要被连带改变,这种情况可以选用观察者模式,被更改的那一个对象很明显就相当于是被观察者对象,而需要连带修改的多个其它对象,就作为多个观察者对象了。

  3、当一个对象必须通知其它的对象,但是你又希望这个对象和其它被它通知的对象是松散耦合的,也就是说这个对象其实不想知道具体被通知的对象,这种情况可以选用观察者模式,这个对象就相当于是被观察者对象,而被它通知的对象就是观察者对象了。

7、JDK支持观察者模式

在JAVA语言的 java.util 库里面,提供了一个Observable类以及一个Observer接口,构成JAVA语言对观察者模式的支持。

Observer:这个接口只定义了一个方法,即update()方法,当被观察者对象的状态发生变化时,被观察者对象的notifyObservers()方法就会调用这一方法。

JDK源码:

public interface Observer {
    void update(Observable o, Object arg);
}

Observable:这个类代表一个被观察者对象。一个被观察者对象可以有多个观察者对象,每个观察者对象都是实现Observer接口的对象。在被观察者发生变化时,会调用Observable的notifyObservers()方法,此方法调用所有的具体观察者的update()方法, 从而使所有的观察者都被通知更新自己。

  java.util.Observable提供公开的方法支持观察者对象,这些方法中有两个对Observable的子类非常重要:一个是setChanged(),另一个是notifyObservers()。

  第一方法setChanged()被调用之后会设置一个内部标记变量,代表被观察者对象的状态发生了变化。

  第二个是notifyObservers(),这个方法被调用时,会调用所有登记过的观察者对象的update()方法,使这些观察者对象可以更新自己。

JDK源码:

public class Observable {
    private boolean changed = false;
    private Vector obs;

    /** Construct an Observable with zero Observers. */

    public Observable() {
        obs = new Vector();
    }

    /**
     * 将一个观察者添加到观察者聚集上面
     */
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }

    /**
     * 将一个观察者从观察者聚集上删除
     */
    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }

    public void notifyObservers() {
        notifyObservers(null);
    }

    /**
     * 如果本对象有变化(那时hasChanged 方法会返回true)
     * 调用本方法通知所有登记的观察者,即调用它们的update()方法
     * 传入this和arg作为参数
     */
    public void notifyObservers(Object arg) {
        Object[] arrLocal;
        
        synchronized (this) {

            if (!changed) return;
                arrLocal = obs.toArray();
                clearChanged();
            }
    
            for (int i = arrLocal.length-1; i>=0; i--)
                ((Observer)arrLocal[i]).update(this, arg);
    }

    /**
     * 将观察者聚集清空
     */
    public synchronized void deleteObservers() {
        obs.removeAllElements();
    }

    /**
     * 将“已变化”设置为true
     */
    protected synchronized void setChanged() {
        changed = true;
    }

    /**
     * 将“已变化”重置为false
     */
    protected synchronized void clearChanged() {
        changed = false;
    }

    /**
     * 检测本对象是否已变化
     */
    public synchronized boolean hasChanged() {
        return changed;
    }

    /**
     * Returns the number of observers of this <tt>Observable</tt> object.
     *
     * @return  the number of observers of this object.
     */
    public synchronized int countObservers() {
        return obs.size();
    }
}

这里用Observer、Observable重写上面的实例↓↓↓:

package com.designpattern.Observer.jdk_realize;

import java.util.Observable;

/**
 * 具体的目标对象,负责把有关状态存入到相应的观察者对象,
 * 并在自己状态发生改变时,通知各个观察者
 * 这里模拟 -- 文章类
 * @author Json<<json1990@foxmail.com>>
 */
public class ConcreteWechatSubject extends Observable {
    /**
     * 文章信息
     */
    private String article;
    
    public String getArticle() {
       return article;
    }
    
    public void setArticle(String article) {
       this.article = article;
       System.out.println("发布文章:"+article+"");
       //状态为状态改变,通知各个观察者 
    //注意在用Java中的Observer模式的时候,下面这句话不可少
this.setChanged(); //通知所有粉丝查看文章 this.notifyObservers(article); } }
package com.designpattern.Observer.jdk_realize;

import java.util.Observable;
import java.util.Observer;

/**
 * 具体观察者对象,实现更新的方法,使自身的状态和目标的状态保持一致
 * 这里模拟 -- 具体粉丝用户对象
 * @author Json<<json1990@foxmail.com>>
 */
public class ConcreteFansObserver implements Observer {
    /**
     * 文章信息
     */
    private String article;

    @Override
    public void update(Observable o, Object article) {
        this.article = article.toString();
        System.out.println("你关注的微信公众号发布文章了!文章标题:"+article.toString()+"");
    }
}

测试:

package com.designpattern.Observer.jdk_realize;

/**
 * 测试
 * @author Json<<json1990@foxmail.com>>
 */
public class Client {
    public static void main(String[] args) {
        //创建微信公众号主题
        ConcreteWechatSubject sub = new ConcreteWechatSubject();
        //创建粉丝观察者
        ConcreteFansObserver obsever = new ConcreteFansObserver();
        ConcreteFansObserver obsever1 = new ConcreteFansObserver();
        //添加观察者
        sub.addObserver(obsever);
        sub.addObserver(obsever1);
        //微信公众号发布文章
        sub.setArticle("《关于建军90周年》");
    }
}

 结果:

发布文章:《关于建军90周年》
你关注的微信公众号发布文章了!文章标题:《关于建军90周年》
你关注的微信公众号发布文章了!文章标题:《关于建军90周年》

PS: 注意在用Java中的Observer模式的时候,下面这句话不可少 this.setChanged();

8、总结

观察者模式的核心是先分清角色、定位好观察者和被观察者、他们是多对一的关系。

实现的关键是要建立观察者和被观察者之间的联系、比如在被观察者类中有个集合是用于存放观察者的、当被检测的东西发生改变的时候就要通知所有观察者。在被观察者中要提供一些对所有观察者管理的一些方法.目的是添加或者删除一些观察者.这样才能让被观察者及时的通知观察者关系的状态已经改变、并且调用观察者通用的方法将变化传递过去。

在实现观察者模式,如果JDK的Observable类和一个Observer接口能满足需求,直接复用即可,无需自己编写抽象观察者、抽象主题类;

但是,java.util.Observable是一个类而不是接口,你必须设计一个类继承它。如果某个类想同时具有Observable类和另一个超类的行为,由于java不支持多重继承。所以这个时候就需要自己实现一整套观察者模式。

 

PS:源码地址   https://github.com/JsonShare/DesignPattern/tree/master 

   

PS:原文地址 http://www.cnblogs.com/JsonShare/p/7270546.html

      

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

可以解密加密数据的片段吗?

设计模式解密(23) - 总结篇

Java常用的设计模式16:常用设计模式之观察者模式(行为型模式)

设计模式之单例模式

Python 观察者模式:示例、提示? [关闭]

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