设计模式解密(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<<[email protected]>>
 */
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<<[email protected]>>
 */
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<<[email protected]>>
 */
public interface FansObserver {
    /**
     * 粉丝接收文章接口
     */
    public void update(String article);
}
package com.designpattern.Observer;

/**
 * 具体观察者对象,实现更新的方法,使自身的状态和目标的状态保持一致
 * 这里模拟 -- 具体粉丝用户对象
 * @author Json<<[email protected]>>
 */
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<<[email protected]>>
 */
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<<[email protected]>>
 */
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<<[email protected]>>
 */
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<<[email protected]>>
 */
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

时间: 2025-01-06 21:52:23

设计模式解密(16)- 观察者模式的相关文章

设计模式(16) 观察者模式(OBSERVER)C++实现

意图: 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新. 动机: 将一个系统设计成一系列相互协作的类有一个常见的副作用:需要维护相关对象之间的一致性. 观察者模式定义一种交互,即发布-订阅: 一个对象当自身状态发生改变时,会发出通知,但是并不知道谁是他的接收者,但每个接收者都会接收到通知,这些接受者称为观察者. 作为对通知的响应,每个观察者都将查询目标状态,然后改变自身的状态以和目标状态进行同步. 使用场景: 使对象封装为独立的改变和使用:

【读书笔记】读《JavaScript设计模式》之观察者模式

一.定义 在事件驱动的环境中,比如浏览器这种持续寻求用户关注的环境中,观察者模式(又名发布者-订阅者(publisher-subscripber)模式)是一种管理人与其任务之间的关系(确切地讲,是对象及其行为和状态之间的关系)的得力工具.用JavaScript的话来说,这种模式的实质就是你可以对程序中某个对象的状态进行观察,并且在其发生改变时能够得到通知. 二.例子 我们需要一个发布者的构造函数,它为该实例定义了一个类型为数组的属性,用来保存订阅者的引用. function Publisher(

设计模式解密(22)- 访问者模式 - 扩展篇(分派的概念)

前言:访问者模式拆分  访问者模式基础篇 :http://www.cnblogs.com/JsonShare/p/7380772.html  访问者模式扩展篇 - 分派的概念: http://www.cnblogs.com/JsonShare/p/7381705.html 1.分派的概念 变量被声明时的类型叫做变量的静态类型(Static Type),有些人又把静态类型叫做明显类型(Apparent Type):而变量所引用的对象的真实类型又叫做变量的实际类型(Actual Type).比如:

设计模式学习之观察者模式(Observer,行为型模式)(7)

1.观察者模式又叫做发布-订阅模式. 2.观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象.这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己. 3.使用观察者模式的好处:维护相关对象间的一致性.我们不希望为了维持一致性而使各类紧密耦合,这样会给维护.扩展和重用都带来不便.什么时候使用观察者模式:当一个对象的改变需要同时改变其他对象的时候,而且它不知道具体有多少对象有待改变时,应该考虑使用观察者模式. private static void M

【C++深入浅出】设计模式学习之观察者模式

前言 前两天学习了weak_ptr以后还是不甚明了,一则需要实际应用去锤炼,二来就是不懂观察者模式. 正文 观察者模式又叫发布-订阅模式,定义了一种一对多的依赖关系,让多个观察者对象同时监听某一主题对象,这个主题对象在状态发生改变时,会通知所有的观察者对象,使他们能够自动更新自己. 通过一张visio的UML图片介绍一下子功能关系. subject类:抽象通知者类,一般用一个抽象类或者接口实现,把所有对观察者的引用都放到一个集合里,每个抽象通知者类可以有任意数量的观察者,抽象通知者类提供一个接口

设计模式-单例模式,观察者模式

序言 咳咳,今天起,我要把对设计模式的理解心得,用全新的案例去分析,分享给大家.希望大家能够喜欢. 观察者模式 举例阐述:游戏情节,一颗小男孩,丢到众多鬼子附近,爆炸啦,根据炸弹的威力计算爆炸后鬼子的血量,假定有些鬼子有防具,有些鬼子没有防具. 分析:这种情况,使用观察者模式是比较理想的,因为观察者模式的就是是处理对象间一对多的依赖关系的,当一个对象发生变化,其它依赖他的对象都要得到通知并更新. 定义:在观察者模式中,上述小男孩被称为主题,而小鬼子们就被称为观察者. 下面我用代码,把举例给演示出

设计模式-16观察者模式

一 观察者模式 当对象间存在一对多关系时,则使用观察者模式(Observer Pattern).比如,当一个对象被修改时,则会自动通知它的依赖对象.观察者模式属于行为型模式. 主要解决: 一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作. 关键代码: 在类里有一个 ArrayList 存放观察者们. 使用场景:一个女孩有三个男朋友,女孩的喜怒哀乐,男朋友们都知道.:( 类图: 二 实现代码 1 创建抽象观察者 Observer.java package com.te

java学习笔记-设计模式16(观察者模式)

意图 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 所有依赖于它的对象都得到通知并被自动更新. public interface Observer { public void update(); } public class Observer1 implements Observer { @Override public void update() { System.out.println("observer1 has received!"); } } public

我理解设计模式C++实现观察者模式Observer Pattern

概述: 近期中国股市起起伏伏,当然了起伏就用商机,小明发现商机后果断想入市,买入了中国证券,他想在电脑client上,网页上,手机上,iPad上都能够查看到该证券的实时行情,这样的情况下我们应该怎么设计我们的软件呢?我们能够这样:小明的全部client上都订阅中国证券这个股票,仅仅要股票一有变化,全部的client都会被通知到而且被自己主动更新. 这就是我们的观察者模式,她定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时, 全部依赖于它的对象都得到通知并被自己主动更新. 类图与实例: