设计模式 -- 观察者模式 (Observer Pattern)


定义:定义对象间一种一对多的依赖关系,使得每当一个对象改变状态时,则所有依赖于它的对象都会得到通知并被自动更新。
通俗来讲,观察者模式就是满足这样的需求:如果一个对象的状态发生改变,某些与它相关的对象也要随之做出相应的变化。

场景描述:在班级上, 班长管理好班级的纪律,若有同学违反纪律,班长就会向班主任汇报情况;在这件事情中,班级同学是被观察者,班长是观察者,班级同学若违反了课堂纪律(说明对象状态改变),那么班长就会做出对应的措施(向班主任汇报情况)。

观察者通用类图:

观察者模式.png

Subject(被观察者):一般来讲为抽象类,定义被观察者必须实现的职责,它必须能够动态的添加,删除观察者。相当于场景描述中的班级同学,可以选择多个班长(观察者)。
Observer(观察者):一般来讲为接口,观察者收到被观察者送来的消息后,会自动进行更新操作,对消息进行处理。相当于场景描述中的班长,处理行为就是向班主任汇报情况。
ConcreteSubject(被观察者实现类):实现被观察者接口定义的抽象方法。
ConcreteObserver(观察者实现类):实现观察者接口定义的抽象方法。

Subject(被观察者)代码:

public abstract class Subject {
   private Vector<Observer1> observer1Vector = new Vector<Observer1>(); // 定义一个集合,用于装观察者。
   public void addObserver(Observer1 observer1){ // 添加观察者
      this.observer1Vector.add(observer1);
   }
   public  void subObserver(Observer1 observer1){ // 删除观察者
      this.observer1Vector.remove(observer1);
   }
   public void notifyObserver(){ //通知所有的观察者
      for (Observer1 observer1 : observer1Vector){
         observer1.update();
      }
   }
   public abstract void doSomething(); //处理业务逻辑
}

ConcreteObserver(观察者实现类)代码:

public class ConcreteSubject extends Subject {
   @Override
   public void doSomething() { 
      System.out.println("班长准备向班主任汇报情况");
      this.notifyObserver(); //通知所有观察者
   }
}

Observer(观察者)代码:

public interface Observer1 {
   public void update();
}

ConcreteObserver(观察者实现类)代码:

public class ConcreteObserver11 implements Observer1 {
   @Override
   public void update() {
      System.out.println("班主任收到情况信息");
   }
}

客户端代码:

public class Client {
   public static void main(String[] args) {
      Subject subject = new ConcreteSubject();
      subject.addObserver(new ConcreteObserver11());
      subject.doSomething();
   }
}
----------------output------------------
班长准备向班主任汇报情况
班主任收到情况信息

观察者模式的优点:
①观察者和被观察者之间是抽象耦合的,不管是增加观察者还是被观察者都是容易扩展的。
②建立一套触发机制,将所有的类串联起来,形成触发链。如上述类图中,被观察者有变动会自动触发观察者行为,如此串联下去。

观察者模式的缺点:
①观察者模式所建立起来的串联触发机制,会严重制约系统的执行效率,因此一般会考虑异步的方式。
②一个被观察者,多个观察者的情况下,开发调试会比较复杂,给开发效率带来影响。

观察者模式的使用场景
①在行为可拆分的各类间进行关联的模式下。
②事件多级触发模式。
③跨系统的消息交换场景,如消息队列的处理机制。

观察者模式的注意事项
①由于串联触发机制的低效率,在观察者模式中建议最多出现一个对象既是被观察者也是观察者,也就是消息最多被转发一次。
②与责任链模式相比,观察者广播链在传播的过程中消息是可以改变的,它是由两个相邻节点协商的消息结构;而责任链模式在消息传递过程中基本保持消息不变,如要改变也只能在原有的消息上进行修正。
③在观察者数量较多,处理时间较慢的情况下,需使用异步,这样就需要考虑线程安全与队列的问题。

Java世界中的观察者模式
在Java中有一个类与接口就提供了观察者模式中被观察者与观察者的角色:java.util.Observable 与 java.util.Observer,接下来我们分析这两个类源码并使用。
接口 java.util.Observer :观察者身份

public interface Observer {
    /**
     * 只要观察对象发生变化,就会调用此方法.
     * 一个称为Observable对象的 notifyObservers 方法的应用程序,让所有对象的观察者都知道该变化。
     * @param   o     observable 对象.
     * @param   arg  传递给 notifyObservers 方法的参数。
     */
    void update(Observable o, Object arg);
}

类 java.util.Observable:被观察者身份

/**
* 这个类代表一个被观察的对象或模型视图范例中的“数据”,它可以被分类为表示应用程序想要观察的对象
* 被观察对象有一个或者多个观察者。观察者是可以通过实现接口 java.util.Observer 的任何对象
* 在一个类 java.util.Observable 的实例状态发生改变后,可以通过调用 Observable 的notifyObservers方法通知给所有的观察者
* 并没有指定通知的发送顺序,Observable类中提供的默认实现将按其注册顺序通知观察者,但子类可能会更改此顺序
* 在单线程中传递通知,可以保证它们的子类按照选择的顺序进行通知
* 请注意,此通知机制与线程无关,并且与类 Object 的 wait,notify 机制完全分离
* 新创建的被观察对象,其观察者集合为空,当且仅当 equals 方法返回值为true时,两个观察者被认为是相同的
*/
public class Observable {
    private boolean changed = false; //判断 被观察者状态是否改变,默认值为false
    private Vector<Observer> obs; //使用Vector集合装载所有的观察者,默认为空

    /**构建一个观察者对象为零个的被观察者集合对象*/
    public Observable() {
        obs = new Vector<>();
    }

    /**
     * 添加观察者对象的方法,只要非空且与已经存在集合中的观察者不一样,那么就被添加进来
     * @param   o   一个添加进来的观察者对象
     * @throws NullPointerException   if the parameter o is null.
     */
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }

    /**
     * 从该对象的观察者集合中删除观察者
     * 如果传递一个null值进来,此方法将不会起作用
     * @param   o   被删除的观察者对象
     */
    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }

    /**
     * 如果这个对象发生改变,如 hasChanged 方法所示,则通知所有观察者,然后调用 clearChanged 方法表明这个对象不再改变
     * 每个观察者都使用两个参数来调用其 update 方法:此被观察者对象和null,换句话说,这个方法相当于:notifyObservers(null)
     */
    public void notifyObservers() {
        notifyObservers(null);
    }

    /**
     * 如果这个对象发生改变,如 hasChanged 方法所示,则通知它的所有观察者,然后调用 clearChanged 方法表明这个对象不再改变
     * 每个观察者都使用两个参数来调用其 update 方法:此被观察者对象和 arg 参数
     */
    public void notifyObservers(Object arg) {
    
        Object[] arrLocal; // 临时数组缓冲区,用作当前观察者状态的快照

        synchronized (this) {
            /* 
             * 我们不希望观察者在持有自己的监视器时将回调变为任意代码。
             * 我们从Vector中提取的每个Observable并存储 Observer 的状态的代码需要同步,但是通知观察者时不会。
             * 竞争条件下最糟糕的情况:①最近添加的观察者将错过正在进行的通知 ②最近未注册的观察者将在未被关心时被错误的通知
             */
            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();
    }

    /**
     * 将这个被观察者标记为已被更改状态,hasChanged 方法现在将返回true
     */
    protected synchronized void setChanged() {
        changed = true;
    }

    /**
     * 表明这个对象不再被改变,或者它已经通知了它所有的观察者它的最近的改变,所以hasChanged 方法现在将返回false
     * 此方法由 notifyObservers 方法自动调用
     */
    protected synchronized void clearChanged() {
        changed = false;
    }

    /**
     * 测试此对象是否已被更改,默认返回false
     * 当且仅当 setChanged 方法比 clearChanged 方法更近期被调用时返回 true,否则返回 false
     */
    public synchronized boolean hasChanged() {
        return changed;
    }

    /**
     * 返回这个对象的观察者数量
     */
    public synchronized int countObservers() {
        return obs.size();
    }
}

使用 java.util.Observable 与 java.util.Observer 演示上面的场景描述

//创建一个同学的接口,里面有吃饭与娱乐两个行为
public interface IClassMate {
   public void haveBreakfast();
   public void haveFun();
}

//实现类实现上面的两个方法,同时继承 java.util.Observable 扮演被观察者角色
public class ClassMate extends Observable implements IClassMate {
   @Override
   public void haveBreakfast() {
      System.out.println("班长看到同学开始吃饭,开始向老师汇报");
      super.setChanged();
      super.notifyObservers("老师!!!同学吃饭去了");
   }
   
   @Override
   public void haveFun() {
      System.out.println("班长看同学开始玩,开始向老师汇报");
      super.setChanged();
      super.notifyObservers("老师!!!同学玩去了");
   }
}

//创建班主任类,实现 java.util.Observer,扮演观察者角色
public class Teacher implements Observer {
   @Override
   public void update(Observable o, Object arg) {
      System.out.println("老师收到班长的信息"+arg.toString());
   }
}

//客户端演示
public class Client {
   public static void main(String[] args) {
      IClassMate classMate = new ClassMate(); //创建一个被观察者
      Observer teacher = new Teacher(); //创建一个观察者
      ((ClassMate) classMate).addObserver(teacher); //被观察者添加观察者
      classMate.haveBreakfast(); //被观察者行为触发
      classMate.haveFun();
   }
}
--------------output------------------
班长看到同学开始吃饭,开始向老师汇报
老师收到班长的信息:老师!!!同学吃饭去了
班长看同学开始玩,开始向老师汇报
老师收到班长的信息:老师!!!同学玩去了

在真实项目中的观察者模式改造
①观察者与被观察者之间的消息沟通
被观察者状态改变会触发观察者的一个行为,同时会传递一个消息给观察者。在实际项目中,观察者的update()方法接受两个参数,一个是被观察者对象,一个是DTO(数据传输对象),DTO一般是JavaBean,由被观察者生成,由观察者消费。
在远程传输中,一般是以XML格式传输。

②观察者的响应方式
观察者包含复杂的逻辑,不仅需要接收来自被观察者的消息,还需要对它们进行逻辑处理,在一个观察者多个被观察者情况下,若观察者消耗时间过长,那么被观察者的时间是不是也相应的延长了呢?这时就需要考虑性能优化。
有两个方法:
(1)采用多线程技术,也就是大家说的异步架构。
(2)缓存技术,提供足够的资源,保证快速响应。

③被观察者尽量自己做主
被观察者的状态改变是否一定要通知给观察者呢?这不一定,得看设计的具体要求。一般情况下,会对被观察者的业务逻辑doSomething方法实现重载,然后增加一个doSomething(boolean isNoitfyObs)方法,决定是否通知观察者。

参考书籍:设计模式之禅 --- 秦小波 著

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容

  • 定义 定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者...
    Code猎人阅读 674评论 0 2
  • 参考资料:菜鸟教程之设计模式 设计模式概述 设计模式(Design pattern)代表了最佳的实践,通常被有经验...
    Steven1997阅读 1,161评论 1 12
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,563评论 18 139
  • 【学习难度:★★★☆☆,使用频率:★★★★★】直接出处:观察者模式梳理和学习:https://github.com...
    BruceOuyang阅读 1,498评论 1 5
  • 最新公司在进行管理和业务上的调整,其中涉及到一次app的转让,在此稍作记录流于后人。 直接切入正题: 概念介绍 什...
    乐意先生阅读 220评论 0 2