【情景模式】
目前需要开发一个大型系统,这个大型系统订了一个生命周期,比如初始化-启动-运行-关闭-销毁,由于此系统比较大,因此在每个生命周期都需要做一定的准备工作,并且后期维护可能会修改这些准备工作。
【代码】
伪代码如下:
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("初始化 二");
}
}
}
但是原生的JDK
中Observable
类观察者列表使用的是Vector
,可能存在效率问题。
以上示例代码均在:https://github.com/dengchengchao/design-pattern
观察者模式
【定义】
定义对象之间的一对多依赖关系,使得每当一个对象状态发生改变时,其依赖对象皆得到通知并被自动更新
属于行为型模式
【UML】
◊ Subject
: 目标,目标又称为主题,它是指被观察的对象
◊ConcreteSubject
: 具体目标,目标类的子类,通常它包含有经常发生改变的数据,当它的状态发生改变时,向它的各个观察者发出通知
◊ Observer
: 观察者,将对观察目标的改变做出反应
◊ ConcreteObserver
: 具体观察者,维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要和具体目标的状态保持一致。
【分析】
观察者模式通过状态类型改变通知观察者,最大的用途便是用于解耦。
优点:
- 观察者模式可以将逻辑紧密的多个模块进行分离
- 观察者模式可以将状态的改变需要执行的操作变得更加简单,并且维护性更加高
- 被观察者可以不用关心观察者的细节以及观察者的数量
缺点:
- 会产生更多类
- 使用观察者模式,使得状态的改变的操作不能依赖于观察者列表的顺序
- 如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间
- 如果系统混乱,可能存在循环依赖的问题
【使用场景】
最大的使用场景便是上面的例子:解耦各个系统,随着各个系统的状态不同,需要执行不同操作。
这其实是Tomcat
源码中LifeCycle
模块的伪代码,Tomcat
多个模块中,每个模块随着不同的生命周期,都需要执行不同操作,并且这些操作可能还能通过配置文件配置,通过观察者模式,能够完美的解决上面的问题。
第二个场景便是名副其实的观察者,比如需要观察某个系统的值等等。
其实,在这里就能体会到,设计模式并不是用来适用于某个场景,而是体会里面的思想。
比如场景一,这其实和观察者有点偏,但是应用到这里确实恰恰合适的。