【12】行为型-观察者模式

1、上下文及定义

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


2、常用场景

为了当对象的状态发生改变时,所有依赖它的对象得到通知并进行相应处理。

被观察的对象称之为Subject,观察者成为Observer

(1)图形化编程

(2)分布式事件处理


3、解决方法

推模型:目标对象主动向观察者推动目标的详细信息,不管观察者是否需要,推动的信息通常是目标对象的全部或部分数据,相当于是在广播通信。

 

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


推模型假定目标对象知道观察者需要的数据,

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

 

推模型可能会使得观察者对象难以复用,因为观察者定义的update方法是按需而定义的,可能无法兼顾没有考虑到的使用情况,当新情况出现的时候,就可能需要提供新的update方法,或者干脆重新实现观察者。

 

拉模型update参数是目标对象本身,是能够传递的最大数据集合了,基本上可以适应各种情况的需要。


4、抽象模型

【12】行为型-观察者模式

5、代码实例

(1)拉模型实例
/**
* 目标对象,作为被观察者
*/
public class Subject {
    /**
     * 用来保存注册的观察者对象,也就是报纸的订阅者
     */
    private List<Observer> readers = new ArrayList<Observer>();
    /**
     * 报纸的读者需要先向报社订阅,先要注册
     * @param reader 报纸的读者 
     * @return 是否注册成功
     */
    public void attach(Observer reader) {
        readers.add(reader);
    }
    /**
     * 报纸的读者可以取消订阅
     * @param reader 报纸的读者
     * @return 是否取消成功
     */
    public void detach(Observer reader) {
        readers.remove(reader);
    }
    /**
     * 当每期报纸印刷出来后,就要迅速的主动的被送到读者的手中,
     * 相当于通知读者,让他们知道
     */
    protected void notifyObservers() {
        for(Observer reader : readers){
            reader.update(this);
        }
    }
}

具体subject

/**
* 报纸对象,具体的目标实现
*/
public class NewsPaper extends Subject{
    /**
     * 报纸的具体内容
     */
    private String content;
    /**
     * 获取报纸的具体内容
     * @return 报纸的具体内容
     */
    public String getContent() {
        return content;
    }
    /**
     * 示意,设置报纸的具体内容,相当于要出版报纸了
     * @param content 报纸的具体内容
     */
    public void setContent(String content) {
        this.content = content;
        //内容有了,说明又出报纸了,那就通知所有的读者
        notifyObservers();
    }
}

观察者

/**
* 观察者,比如报纸的读者
*/
public interface Observer {
    /**
     * 被通知的方法
     * @param subject 具体的目标对象,可以获取报纸的内容
     */
    public void update(Subject subject);
}

观察者实现

/**
* 真正的读者,为了简单就描述一下姓名
*/
public class Reader implements Observer{
    /**
     * 读者的姓名
     */
    private String name;

    public void update(Subject subject) {
        //这是采用拉的方式
        System.out.println(name+"收到报纸了,阅读先。内容是==="+((NewsPaper)subject).getContent());
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

客户端

public class Client {
    public static void main(String[] args) {
        //创建一个报纸,作为被观察者
        NewsPaper subject = new NewsPaper();

        //创建阅读者,也就是观察者
        Reader reader1 = new Reader();
        reader1.setName("张三");

        Reader reader2 = new Reader();
        reader2.setName("李四");

        Reader reader3 = new Reader();
        reader3.setName("王五");

        //注册阅读者
        subject.attach(reader1);
        subject.attach(reader2);
        subject.attach(reader3);

        //要出报纸啦
        subject.setContent("本期内容是观察者模式");
    }
}


(2)推模型实例

观察者

/**
* 观察者,比如报纸的读者
*/
public interface Observer {
    /**
     * 被通知的方法,直接把报纸的内容推送过来
     * @param content 报纸的内容
     */
    public void update(String content);
}

观察者实现

/**
* 真正的读者,为了简单就描述一下姓名
*/
public class Reader implements Observer{
    /**
     * 读者的姓名
     */
    private String name;

    public void update(String content) {
        //这是采用推的方式
        System.out.println(name+"收到报纸了,阅读先。内容是==="+content);
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

Subject类

/**
* 目标对象,作为被观察者,使用推模型
*/
public class Subject {
    /**
     * 用来保存注册的观察者对象,也就是报纸的订阅者
     */
    private List<Observer> readers = new ArrayList<Observer>();
    /**
     * 报纸的读者需要先向报社订阅,先要注册
     * @param reader 报纸的读者 
     * @return 是否注册成功
     */
    public void attach(Observer reader) {
        readers.add(reader);
    }
    /**
     * 报纸的读者可以取消订阅
     * @param reader 报纸的读者
     * @return 是否取消成功
     */
    public void detach(Observer reader) {
        readers.remove(reader);
    }
    /**
     * 当每期报纸印刷出来后,就要迅速的主动的被送到读者的手中,
     * 相当于通知读者,让他们知道
     * @param content 要主动推送的内容
     */
    protected void notifyObservers(String content) {
        for(Observer reader : readers){
            reader.update(content);
        }
    }
}

subject类实现

/**
* 报纸对象,具体的目标实现
*/
public class NewsPaper extends Subject{
    /**
     * 报纸的具体内容
     */
    private String content;
    /**
     * 获取报纸的具体内容
     * @return 报纸的具体内容
     */
    public String getContent() {
        return content;
    }
    /**
     * 示意,设置报纸的具体内容,相当于要出版报纸了
     * @param content 报纸的具体内容
     */
    public void setContent(String content) {
        this.content = content;
        //内容有了,说明又出报纸了,那就通知所有的读者
        notifyObservers(content);
    }
}


(3)JDK观察者模式

Java的util包里面有一个类Observable,它实现了大部分我们需要的目标的功能;

还有一个Observer,定义了update方法,就是观察者的接口。

 

1、不需要再定义Subject和Observer类

2、具体的Subject实现类里面不再需要维护Observer的注册信息

3、触发通知方式有点变化,需要先调用setChanged方法

4、具体的Observer实现类里面,update方法能同时指出推模型和拉模型


Subject实现类

/**
* 报纸对象,具体的目标实现
*/
public class NewsPaper extends java.util.Observable{
    /**
     * 报纸的具体内容
     */
    private String content;
    /**
     * 获取报纸的具体内容
     * @return 报纸的具体内容
     */
    public String getContent() {
        return content;
    }
    /**
     * 示意,设置报纸的具体内容,相当于要出版报纸了
     * @param content 报纸的具体内容
     */
    public void setContent(String content) {
        this.content = content;
        //内容有了,说明又出报纸了,那就通知所有的读者
        //注意在用Java中的Observer模式的时候,这句话不可少
        this.setChanged();
        //然后主动通知,这里用的是推的方式
        this.notifyObservers(this.content);
        //如果用拉的方式,这么调用
        //this.notifyObservers();
    }
}

Obsever实现类

/**
* 真正的读者,为了简单就描述一下姓名
*/
public class Reader implements java.util.Observer{
    /**
     * 读者的姓名
     */
    private String name;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

    public void update(Observable o, Object obj) {
        //这是采用推的方式
        System.out.println(name+"收到报纸了,阅读先。目标推过来的内容是==="+obj);
        //这是获取拉的数据
        System.out.println(name+"收到报纸了,阅读先。主动到目标对象去拉的内容是==="
        +((NewsPaper)o).getContent());
    }

}

客户端

public class Client {
    public static void main(String[] args) {
        //创建一个报纸,作为被观察者
        NewsPaper subject = new NewsPaper();

        //创建阅读者,也就是观察者
        Reader reader1 = new Reader();
        reader1.setName("张三");

        Reader reader2 = new Reader();
        reader2.setName("李四");

        Reader reader3 = new Reader();
        reader3.setName("王五");

        //注册阅读者
        subject.addObserver(reader1);
        subject.addObserver(reader2);
        subject.addObserver(reader3);

        //要出报纸啦
        subject.setContent("本期内容是观察者模式");
    }
}

(4)Observable类
/**
     * If this object has changed, as indicated by the
     * <code>hasChanged</code> method, then notify all of its observers
     * and then call the <code>clearChanged</code> method to indicate
     * that this object has no longer changed.
     * <p>
     * Each observer has its <code>update</code> method called with two
     * arguments: this observable object and the <code>arg</code> argument.
     *
     * @param   arg   any object.
     * @see     java.util.Observable#clearChanged()
     * @see     java.util.Observable#hasChanged()
     * @see     java.util.Observer#update(java.util.Observable, java.lang.Object)
     */
    public void notifyObservers(Object arg) {
        /*
         * a temporary array buffer, used as a snapshot of the state of
         * current Observers.
         */
        Object[] arrLocal;

        synchronized (this) {
            /* We don't want the Observer doing callbacks into
             * arbitrary code while holding its own Monitor.
             * The code where we extract each Observable from
             * the Vector and store the state of the Observer
             * needs synchronization, but notifying observers
             * does not (should not).  The worst result of any
             * potential race-condition here is that:
             * 1) a newly-added Observer will miss a
             *   notification in progress
             * 2) a recently unregistered Observer will be
             *   wrongly notified when it doesn't care
             */
            if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }

        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }
    
     /**
     * Marks this <tt>Observable</tt> object as having been changed; the
     * <tt>hasChanged</tt> method will now return <tt>true</tt>.
     */
    protected synchronized void setChanged() {
        changed = true;
    }

    /**
     * Indicates that this object has no longer changed, or that it has
     * already notified all of its observers of its most recent change,
     * so that the <tt>hasChanged</tt> method will now return <tt>false</tt>.
     * This method is called automatically by the
     * <code>notifyObservers</code> methods.
     *
     * @see     java.util.Observable#notifyObservers()
     * @see     java.util.Observable#notifyObservers(java.lang.Object)
     */
    protected synchronized void clearChanged() {
        changed = false;
    }

  采用倒序的方式通知。

  采用JDK的观察者模式的限制:

A、继承Observable类,如果需要多继承的话,只能采用新类继承它,然后组合新累,调用setChanged方法,因为该方法时protected的

B、如果需要更改通知顺序的话,需要自己专门来处理


你可能感兴趣的:(【12】行为型-观察者模式)