美文网首页
设计模式 -- 观察者模式 (Observer Pattern)

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

作者: 小杰的快乐时光 | 来源:发表于2018-08-18 20:53 被阅读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)方法,决定是否通知观察者。

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

    相关文章

      网友评论

          本文标题:设计模式 -- 观察者模式 (Observer Pattern)

          本文链接:https://www.haomeiwen.com/subject/bafqiftx.html