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

2018-08-18  本文已影响0人  小杰的快乐时光

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

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

观察者通用类图:

观察者模式.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)方法,决定是否通知观察者。

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

上一篇下一篇

猜你喜欢

热点阅读