设计模式之观察者模式

【情景模式】
目前需要开发一个大型系统,这个大型系统订了一个生命周期,比如初始化-启动-运行-关闭-销毁,由于此系统比较大,因此在每个生命周期都需要做一定的准备工作,并且后期维护可能会修改这些准备工作。

【代码】

伪代码如下:

public class XXSystem {

    public void init() {

        System.out.println("初始化一。。。");

        System.out.println("初始化二。。。");

        System.out.println("初始化三。。。");
    }


    public void start() {

        System.out.println("启动一。。。");

        System.out.println("启动二。。。");

        System.out.println("启动三。。。");
    }


    public void stop() {

        System.out.println("关闭一。。。");

        System.out.println("关闭二。。。");

        System.out.println("关闭三。。。");
    }

    public void destory() {

        System.out.println("销毁一。。。");

        System.out.println("销毁二。。。");

        System.out.println("销毁三。。。");
    }


    public static void main(String[] args) {
        XXSystem xxSystem=new XXSystem();
        xxSystem.init();
        xxSystem.start();
        xxSystem.stop();
        xxSystem.destory();
    }
}

当然,上面的只是伪代码,不过这种情况在实际开发中肯定存在,随着系统的扩展,每个步骤需要执行的东西也越来越多。这个类也会越来越臃肿,维护难度大大增加,并且每此想修改任何一个步骤,都需要修改这个类。

现在我们的选择可以是将每个生命周期都拆分成一个单独的类,这样但是能稍微减轻一定,但是每次想要增加一个步骤的细节,比如 增加一个初始化四或者删除初始化三,依然要修改原本的类。

【优化】

其实,仔细分析上面的需求,我们可以发现整个系统的执行,都是随着系统的生命周期的改变而开始的,比如系统初始化,就会执行所有的初始化的方法。这个时候,我们可以将这个生命周期定义出来

public enum LifeCycle {
    INIT,
    START,
    STOP,
    DESTORY
}

紧接着,我们可以定义一个接口,这个接口用于定义生命周期修改时需要执行的操作:

public interface Observer {
    void update(LifeCycle lifeCycle);
}

在定义一个用于通知更新状态的抽象类:

public abstract class Subject {

    private List<Observer> observers=new ArrayList<>();

    public void attach(Observer observer){
        observers.add(observer);
    }

    public void notifyAllObservers(LifeCycle lifeCycle){
        for (Observer observer : observers) {
            observer.update(lifeCycle);
        }
    }

    public void detach(Observer observer){
        observers.remove(observer);
    }
}

然后看情况实现一些初始化,开始等需要的操作:

public class InitOther implements Observer {
    @Override
    public void update(LifeCycle lifeCycle) {
         if (lifeCycle==LifeCycle.INIT){
             System.out.println("初始化 一");
         }
    }
}
public class Init implements Observer {
    @Override
    public void update(LifeCycle lifeCycle) {
        if (lifeCycle == LifeCycle.INIT) {
            System.out.println("初始化 二");
        }
    }
}

最后,便是真正的使用:

    public static void main(String[] args) {

        XXSystem xxSystem=new XXSystem();
        Init init=new Init();
        InitOther initOther=new InitOther();

        xxSystem.attach(init);
        xxSystem.attach(initOther);
        xxSystem.init();
        xxSystem.start();
        xxSystem.stop();
        xxSystem.destory();
    }


可以看出来,上面的代码最终依然实现了和第一版一样的功能,但是优点如下:

  • 每个模块相互独立,修改互不影响
  • 每个模块想要删除或者增加新的功能都不会影响以前的代码,耦合性非常低

上面便是观察者模式。


观察者模式在JDK中有原生的支持:

//被观察者只用继承Observable类即可
public class XXSystem extends Observable {

    public void init() {
        this.setChanged();
        notifyObservers(LifeCycle.INIT);

    }


    public void start() {
        this.setChanged();
        notifyObservers(LifeCycle.INIT);

    }


    public void stop() {
        this.setChanged();
        notifyObservers(LifeCycle.INIT);


    }

    public void destory() {
        this.setChanged();
        notifyObservers(LifeCycle.INIT);

    }

}
//观察者实现Observer接口
public class Init implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        if (arg == LifeCycle.INIT) {
            System.out.println("初始化 二");
        }
    }
}

但是原生的JDKObservable类观察者列表使用的是Vector,可能存在效率问题。


以上示例代码均在:https://github.com/dengchengchao/design-pattern

观察者模式

【定义】

定义对象之间的一对多依赖关系,使得每当一个对象状态发生改变时,其依赖对象皆得到通知并被自动更新

属于行为型模式

【UML】

image

Subject: 目标,目标又称为主题,它是指被观察的对象

ConcreteSubject: 具体目标,目标类的子类,通常它包含有经常发生改变的数据,当它的状态发生改变时,向它的各个观察者发出通知

Observer: 观察者,将对观察目标的改变做出反应

ConcreteObserver: 具体观察者,维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致。

【分析】

观察者模式通过状态类型改变通知观察者,最大的用途便是用于解耦。

优点:

  • 观察者模式可以将逻辑紧密的多个模块进行分离
  • 观察者模式可以将状态的改变需要执行的操作变得更加简单,并且维护性更加高
  • 被观察者可以不用关心观察者的细节以及观察者的数量

缺点:

  • 会产生更多类
  • 使用观察者模式,使得状态的改变的操作不能依赖于观察者列表的顺序
  • 如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间
  • 如果系统混乱,可能存在循环依赖的问题

【使用场景】

最大的使用场景便是上面的例子:解耦各个系统,随着各个系统的状态不同,需要执行不同操作。

这其实是Tomcat源码中LifeCycle模块的伪代码,Tomcat多个模块中,每个模块随着不同的生命周期,都需要执行不同操作,并且这些操作可能还能通过配置文件配置,通过观察者模式,能够完美的解决上面的问题。

第二个场景便是名副其实的观察者,比如需要观察某个系统的值等等。

其实,在这里就能体会到,设计模式并不是用来适用于某个场景,而是体会里面的思想。

比如场景一,这其实和观察者有点偏,但是应用到这里确实恰恰合适的。