Skip to content

观察者模式

ZHI-XINHUA edited this page Jan 11, 2019 · 1 revision

观察者模式

   观察者模式是对象的行为模式,又叫发布-订阅模式(publish/subscribe)、模型-视图模式(model/view)、源-监听器模式(source/listener)或者从属者模式(dependents)。

    观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。这个主题对象在状态上发生改变时,会通知所有观察者对象,使它们能够自动更新自己。   

观察者模式结构

观察者模式的实现角色有:

  • 抽象主题(Subject)角色: 主题角色把所有对观察者对象的引用保存在一个集合中(比如Vector),每个主题都有任何数量的观察者。抽象主题提供可以增加、删除观察者对象的操作(可以是接口,也可以是实现方法)
  • 抽象观察者(Observer)角色: 为所有的具体观察者定义一个接口,在主题发生变化得到通知后可以触发自己行为。在示例代码中,更新接口只有一个方法(update方法)。
  • 具体主题(ConcreteSubject)角色: 将有关状态存入具体观察对象;在具体主题内部状态改变时,给所有登记过的观察者发送通知。具体主题角色又叫作具体被观察者角色。通常是一个具体子类实现。
  • 具体观察者(ConcreteObserver)角色: 具体观察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态相协调。如果需要,具体观察者角色可以保存一个指向具体主题的应用。

一、读者订阅报刊栗子

uml:

(1)Observable 抽象主题角色,管理观察者(登记、删除、发送通知观察者)

/**
 * Created by xh.zhi on 2018-9-20.
 * 被观察者父类角色:管理观察者(登记、删除、发送通知观察者)
 */
public class Observable {
    /**存放所有登记的观察者**/
    Vector<Observer> vector = new Vector<Observer>();

    /**
     * 登记观察者
     * @param observer
     */
    public void addObserver(Observer observer){
        vector.add(observer);
    }

    /**
     * 删除观察者
     * @param observer
     */
    public void deleteObserver(Observer observer){
        vector.remove(observer);
    }

    /**
     * 发送通知给观察者
     */
    public void notifyObservers(){
        ListIterator<Observer> list = vector.listIterator();
        while(list.hasNext()){
            Observer observer = list.next();
            observer.update(this);
        }
    }
}

(2)、Observer 抽象观察者角色,为所有的具体观察者定义一个接口,在主题发生变化得到通知后可以触发自己行为

/**
 * Created by xh.zhi on 2018-9-20.
 *  观察者接口:提供一个统一的观察者做出相应行为的方法
 */
public interface Observer {
    //获取观察目标(被观察对象)发送变化的信息
    void update(Observable observable);
}

(3)、Newspapers 报刊类,具体被观察者角色。一旦有新报刊推出,立即通知读者。

/**
 * Created by xh.zhi on 2018-9-20.
 *  报刊类,被观察者角色。
 */
public class Newspapers extends Observable {
    /**刊报名称**/
    private String newspaperName;

    /**
     * 提供获取最新一期报刊
     * @return
     */
    public String getNewspaperName(){
        return newspaperName;
    }

    /**
     * 推出最新刊报
     * @param newspaperName
     */
    public void publishWorks(String newspaperName){
        this.newspaperName = newspaperName;
        System.out.println("推出新一期刊报:"+newspaperName);

        //通知给位订阅者
        this.notifyObservers();
    }
}

(4)、Reader 读者类,观察者角色,观察到喜爱的报刊有更新则立即采取行动订阅购买

/**
 * Created by xh.zhi on 2018-9-20.
 * 读者,观察者角色
 */
public class Reader implements Observer{
    /**读者名字**/
    String name;
    /**读者喜爱的报刊**/
    String myLoveNewspaper;

    public Reader(String name){
        this.name = name;
    }

    /**
     * 有最新一期报刊推出,则触发方法
     * @param observable
     */
    @Override
    public void update(Observable observable) {
        //取得最新推出消息
        if(observable instanceof Newspapers){
            myLoveNewspaper = ((Newspapers)observable).getNewspaperName();

            //马上订阅
            doSubscription();
        }
    }

    /**
     * 订阅报刊
     */
    private void doSubscription(){
        System.out.println("我是"+this.name+",帮我订阅一本:"+myLoveNewspaper);
    }
}

测试:

public static void main(String[] args) {
        // 三位读者
        Reader zhangshang = new Reader("zhangshan");
        Reader lisi = new Reader("lisi");
        Reader wuwang = new Reader("wuwang");

        // 报社
        Newspapers newspapers = new Newspapers();

        //三位读者关注了报社
        newspapers.addObserver(zhangshang);
        newspapers.addObserver(lisi);
        newspapers.addObserver(wuwang);

        //报社推出第一期新报刊,三位读者就去订购
        newspapers.publishWorks("第一期,舌尖上的中国之广东美食");


        System.out.println();

        // lisi觉得不咋地,下一期打算不订阅了
        newspapers.deleteObserver(lisi);

        //报社推出第二期新报刊,三位读者就去订购
        newspapers.publishWorks("第二期,都市生活");
    }

结果如下:

推出新一期刊报:第一期,舌尖上的中国之广东美食
我是zhangshan,帮我订阅一本:第一期,舌尖上的中国之广东美食
我是lisi,帮我订阅一本:第一期,舌尖上的中国之广东美食
我是wuwang,帮我订阅一本:第一期,舌尖上的中国之广东美食

推出新一期刊报:第二期,都市生活
我是zhangshan,帮我订阅一本:第二期,都市生活
我是wuwang,帮我订阅一本:第二期,都市生活

二、java提供的观察者模式的支持改造上面例子

java提供的观察者接口Observer:

当观察到的对象发生更改时,将调用update()方法,Observer代码如下:

public interface Observer {
    /**
     * This method is called whenever the observed object is changed. An
     * application calls an <tt>Observable</tt> object's
     * <code>notifyObservers</code> method to have all the object's
     * observers notified of the change.
     *
     * @param   o     the observable object.
     * @param   arg   an argument passed to the <code>notifyObservers</code>
     *                 method.
     */
    void update(Observable o, Object arg);
}

java提供的被观察者父类Observable

被观察者类都是java.util.Observable类的子类。Observable提供公开的方法支持观察者对象,这些方法中有两个对Observable子类非常重要:

(1)setChanged() 被调用之后会设置一个内部标志变量changed,代表被观察者对象的状态发生了变化

(2)notifyObservers() 被调用时,会调用所有登记过的观察者对象的update()方法,使观察则对象可以更新自己。

还有其它方法,比如,添加观察者、删除观察者等等。类图如下:

利用java提供的观察者模式改造上面例子:

(1)Newspapers报社类,被观察者角色。继承jdk 的Observable类

**
 * 报社,被观察者角色。继承jdkObservable类
 */
public class Newspapers extends Observable {
    /**刊报名称**/
    private String newspaperName;

    /**
     * 提供获取最新一期报刊
     * @return
     */
    public String getNewspaperName(){
        return newspaperName;
    }

    /**
     * 推出最新刊报
     * @param newspaperName
     */
    public void publishWorks(String newspaperName){
        this.newspaperName = newspaperName;
        System.out.println("推出新一期刊报:"+newspaperName);
        //设置为状态已改变
        setChanged();
        //通知给位订阅者
        this.notifyObservers();
    }
}

(2)、Reader读者类,观察者角色。 实现jdk 的Observer接口

/**
 * 读者,观察者角色。 实现jdk 的Observer接口
 */
public class Reader implements Observer {
    /**读者名字**/
    String name;
    /**读者喜爱的报刊**/
    String myLoveNewspaper;

    public Reader(String name){
        this.name = name;
    }


    /**
     * 有最新一期报刊推出,则触发方法
     * @param observable  被观察者
     * @param message 变化信息
     */
    @Override
    public void update(Observable observable, Object message) {
        if(observable instanceof Newspapers){
            myLoveNewspaper = ((Newspapers)observable).getNewspaperName();

            //马上订阅
            doSubscription();
        }

    }

    /**
     * 订阅报刊
     */
    private void doSubscription(){
        System.out.println("我是"+this.name+",帮我订阅一本:"+myLoveNewspaper);
    }
}

测试:

public static void main(String[] args) {
        // 三位读者
        Reader zhangshang =new Reader("zhangshan");
        Reader lisi = new Reader("lisi");
        Reader wuwang = new Reader("wuwang");

        // 报社
        Newspapers newspapers = new Newspapers();

        //三位读者关注了报社
        newspapers.addObserver(zhangshang);
        newspapers.addObserver(lisi);
        newspapers.addObserver(wuwang);

        //报社推出第一期新报刊,三位读者就去订购
        newspapers.publishWorks("第一期,舌尖上的中国之广东美食");


        System.out.println();

        // lisi觉得不咋地,下一期打算不订阅了
        newspapers.deleteObserver(lisi);

        //报社推出第二期新报刊,三位读者就去订购
        newspapers.publishWorks("第二期,都市生活");
    }

运行结果和上面例子一模一样。

最后总结观察者模式的优缺点

优点

  • 观察者模式在被观察者和观察者之间建立一个抽象的耦合。
  • 观察者模式支持广播通信。被观察者会向所有登记过的观察者发出通知。

缺点

  • 如果一个被观察者对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
  • 如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
  • 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。