解耦关联对象——观察者模式详解
2018/11/17 13:00:10 来源:cnblogs.com/takumicx 作者:takumicx

1. 观察者模式简介

在软件开发中,观察者模式是使用频率最高的设计模式之一,如果你做过web开发,对它应该更不会陌生,因为典型的MVC架构就是对观察者模式的一种延伸。在软件开发中经常会碰到这种困境:系统由若干个相互协作的类构成,类之间常有一对多的依赖关系,当被依赖对象的状态变化时,其他所有依赖对象都要发生改变。以MVC为例,模型(Model)对象封装了数据,视图(View)对象对数据进行渲染和进行图形表示。当模型中的数据改变时,视图应该马上得到反馈从而改变其显示的内容。我们需要维护这种具有依赖关系的对象之间的一致性,又不希望为了维护这种一致性导致类之间紧密耦合。而观察者模式正式对这一困境的回答。观察者模式的的最大好处是可以实现具有关联关系的对象之间的解耦,使得双方可以独立的进行扩展和变化,使得系统具有更好的弹性。

2. 观察者模式详解

2.1 观察者模式定义

观察者模式定义了对象之间的一对多依赖关系,每当对象改变状态,所有依赖于它的对象都会得到通知并被自动更新。

2.2观察者模式的结构

观察者模式的结构相对简单,可以用<Head First设计模式>中的一张图来描述

观察者模式的主要角色:

2.3 观察者模式的简单实现

观察者模式又被称为发布订阅模式,以客户订阅报纸为例,客户相当于观察者,而报社则是被观察者。客户可以向报社订阅报纸,也可以取消订阅。当报社有新报纸出版时,就会将报纸发送给订阅的客户。

/**
 * @author: takumiCX
 * @create: 2018-10-29
 **/
public abstract class Subject {

    //观察者集合
    private CopyOnWriteArrayList<Observer> observers=new CopyOnWriteArrayList<>();


    //注册观察者
    protected  void registerObserver(Observer observer){
        observers.add(observer);
    }

    //移除观察者
    protected boolean removeObserver(Observer observer){
        return observers.remove(observer);
    }


    /**
    * 通知观察者
    * @param msg 发送给观察者的消息
    */
    protected void notifyObservers(String msg){

        for(Observer observer:observers){
            observer.update(msg);
        }
    }

    /**
    * 通知观察者
    */
    protected void notifyObservers(){

        for(Observer observer:observers){
            observer.update();
        }
    }

}

该抽象类内部维护了一个线程安全的CopyOnWriteArrayList来存储观察者集合,并没有使用Vector或者SynchronizedList等常见同步容器,在需要频繁增删观察者的情况下可以一定程度提升性能。

/**
 * @author: takumiCX
 * @create: 2018-10-29
 **/
public class NewsPaperSubject extends Subject {

    //报纸的期号
    private String date;

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }

    /**
    * 通知订阅的客户接收新一期的报纸
    */
    public void postNewPublication(){

        notifyObservers(date);
    }

}

具体的主题实现类继承了主题抽象类,并添加了一个状态变量,表示报纸的期号,在通知订阅的客户时需要将该信息也一起传过去。postNewPublication()方法当有新一期的报纸发行时,会通过调用该方法对订阅的客户进行通知。

/**
 * @author: takumiCX
 * @create: 2018-10-29
 **/
public interface Observer {

    void update(String msg);

    void update();

}

/**
 * @author: takumiCX
 * @create: 2018-10-29
 **/
public class CustomerObserver implements Observer {

    //客户姓名
    private String name;

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

    @Override
    public void update(String msg) {
        System.out.println(name+" 您好!"+msg+" 期的报纸已发送,请注意接收!");
    }

    @Override
    public void update() {

    }
}

/**
 * @author: takumiCX
 * @create: 2018-10-29
 **/
public class Test {

    public static void main(String[] args) {

        //报社(主题)
        NewsPaperSubject newsPaperSubject = new NewsPaperSubject();

        //客户1(观察者)
        CustomerObserver observer1 = new CustomerObserver("赵云");

        //客户2(观察者)
        CustomerObserver observer2 = new CustomerObserver("马超");

        CustomerObserver observer3 = new CustomerObserver("张飞");
        //向主题注册观察者
        newsPaperSubject.registerObserver(observer1);
        newsPaperSubject.registerObserver(observer2);
        newsPaperSubject.registerObserver(observer3);

        //报纸的期号
        String date="2018-10-29";
        newsPaperSubject.setDate(date);
        //通知所有订阅的客户接收报纸
        newsPaperSubject.postNewPublication();
    }
}

2.4 使用JDK内置的观察者实现

JDK内置了对观察者模式的支持,只要继承或者实现相应的抽象类或接口

/**
 * @author: takumiCX
 * @create: 2018-10-29
 **/
public class JDKNewsPaperObservable extends Observable {


    //报纸的期号
    private String date;

    public String getDate() {
        return date;
    }

    public void setDate(String date) {
        this.date = date;
    }


    public void postNewPublication(){

        //将状态改变的标志位置位true
        setChanged();
        //通知所有观察者
        notifyObservers(date);
    }
}

JKD中对主题通过抽象类Observable进行了抽象,实现自定义主题只要继承该抽象类即可。注意该抽象类内部有一个标注主题状态是否改变的标志位,默认为false

private boolean changed = false;

在通知观察者前必须先通过调用setChanged()方法将该标志位置为true。在通知观察者进行更新的方法被调用后,该标志位会被重新置为false。

/**
 * @author: takumiCX
 * @create: 2018-10-29
 **/
public class JDKCustomerObserver implements Observer {

    //客户姓名
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

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

    @Override
    public void update(Observable o, Object arg) {

        System.out.println(name+" 您好!"+arg+" 期的报纸已发送,请注意接收!");
    }
}

/**
 * @author: takumiCX
 * @create: 2018-10-29
 **/
public class Test2 {

    public static void main(String[] args) {
        //报社(主题)
        JDKNewsPaperObservable jdkNewsPaperObservable = new JDKNewsPaperObservable();

        //客户1(观察者)
        JDKCustomerObserver observer1 = new JDKCustomerObserver("赵云");

        //客户2(观察者)
        JDKCustomerObserver observer2 = new JDKCustomerObserver("马超");

        JDKCustomerObserver observer3 = new JDKCustomerObserver("张飞");
        //向主题注册观察者
        jdkNewsPaperObservable.addObserver(observer1);
        jdkNewsPaperObservable.addObserver(observer2);
        jdkNewsPaperObservable.addObserver(observer3);

        //报纸的期号
        String date="2018-10-29";
        jdkNewsPaperObservable.setDate(date);
        //通知所有订阅的客户接收报纸
        jdkNewsPaperObservable.postNewPublication();

    }
}

3 使用观察者模式需要注意的地方

1.多个观察者默认是被顺序调用而执行的,当一个观察者的业务逻辑执行卡顿,或者执行时间过长,会导致后续观察者的业务逻辑执行被延迟,也会影响整体的执行效率。
解决办法:采用异步的方式进行处理,比如将观察者的业务逻辑放到线程池中去执行。

2.当多个对象既是观察者又是被观察者将导致系统难以调试和维护。
解决办法:不允许观察者模式中存在既是观察者又是被观察者的对象。

4. 总结

当存在相互关联的对象,即某些对象状态的改变会导致其他对象产生相应的变化。使用观察者模式可以方便的维护关联对象间行为的一致性,同时使其保持松耦合状态,这样双方就可以相对独立的进行扩展和变化,使得系统更具弹性。


6

本栏最新