1 观察者模式的定义
观察者模式:Observer Pattern,也叫发布-订阅模式(Publish-Subscribe Pattern)。具体定义是:定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。
观察者模式的通用类图:
- Subject被观察者
定义被观察者必须实现的职责,它必须能够动态地增加、取消观察者。一般是抽象类或者接口,仅仅完成作为被观察者必须实现的职责:管理观察者并通知观察者。 - ConcreteSubject具体的被观察者
定义被观察者自己的业务逻辑,同时定义对哪些事件进行通知。 - Observer观察者
观察者接收到消息后,就进行update操作,对接收到的信息进行处理。 - ConcreteObserver具体的观察者
每个观察者在接收到消息后的处理应该是不同的,每个观察者有自己独特的处理逻辑。
2 观察者模式通用代码示例
- 被观察者
- 抽象的被观察者
@Slf4j
public abstract class Subject {
/**
* 定义一个观察者数组
*/
private Vector<Observer> observers = new Vector<>();
/**
* 增加一个观察者
*
* @return true:增加成功;false:增加失败
*/
public boolean addObserver(Observer observer) {
return this.observers.add(observer);
}
/**
* 删除一个观察者
*
* @return true:删除成功;false:删除失败
*/
public boolean removeObserver(Observer observer) {
return this.observers.remove(observer);
}
/**
* 通知所有的观察者
*/
public void notifyAllObservers() {
log.info("{}通知所有的观察者.", this.getClass().getSimpleName());
for (Observer observer : observers) {
observer.update();
}
}
}
- 具体被观察者
@Slf4j
public class ConcreteSubject extends Subject {
/**
* 具体被观察者独特的业务逻辑
*/
public void uniqueMethod() {
//具体业务
log.info("具体业务逻辑{}.uniqueMethod", this.getClass().getSimpleName());
super.notifyAllObservers();
}
}
- 观察者
- 观察者接口
观察者一般是一个接口。
public interface Observer {
/**
* 观察者的操作
*/
public void update();
}
- 具体观察者
每一个实现该接口的实现类都是具体观察者。
@Slf4j
public class ConcreteObserver implements Observer {
/**
* 实现具体的观察者接口,具有独特的业务
*/
@Override
public void update() {
log.info("观察者{}接收到消息,正在进行处理...", this.getClass().getSimpleName());
}
}
- 场景类
@Slf4j
public class Client {
public static void main(String[] args) {
//创建一个被观察者
ConcreteSubject subject = new ConcreteSubject();
//定义一个观察者
Observer observer = new ConcreteObserver();
//观察者观察被观察者
subject.addObserver(observer);
//被观察者进行活动,观察者进行观察者
subject.uniqueMethod();
}
}
3 观察者模式的优缺点
3.1 优点
- 观察者和被观察者之间是抽象耦合
观察者和被观察者之间通过抽象耦合,这样设计在增加观察者或被观察者的时候,都非常容易扩展,不修改原有代码只需要增加具体实现就可以进行扩展,符合“开闭原则”。 - 建立一套触发机制
观察者模式可以更方便地实现具体业务中的复杂逻辑关系,形成逻辑链条。
3.2 缺点
- 开发效率低
一个被观察者、多个观察者,开发和调试都会比较复杂,在大型项目中影响开发效率。 - 执行效率低
在Java中消息的通知默认是顺序执行,一个观察者出错,会影响整体的执行效率,因此一般可以考虑采用异步方式。尤其是在多级触发的时候,执行效率更会受影响。
因此在设计时注意考虑影响。
4 观察者模式的应用
4.1 应用场景
- 关联行为场景
关联行为是可拆分的,而不是“组合”关系。 - 事件多级触发场景
- 跨系统的消息交换场景
如消息队列的处理机制。
4.2 使用观察者模式的注意事项
使用观察者模式有两个重点问题需要解决。
- 广播链问题
观察者模式中的观察者同时也可以是被观察者,因此很容易形成逻辑链。逻辑链一旦建立,逻辑就比较复杂,可维护性非常差。
根据经验建议:
在一个观察者模式中,最多出现一个对象既是观察者也是被观察者,也就是说消息最多转发一次(传递两次),还是比较好控制的。
广播链和责任链模式最大的区别是:
- 观察者广播链:消息在传播的过程中是随时更改的,它是由相邻的两个节点协商的消息结构。
- 责任链模式:消息在传递的过程中基本上保持不变。如果要改变,也只有在原有的消息上进行修正。
- 异步处理问题
如果被观察者发生动作了,观察者要做出响应。如果观察者比较多,就要用异步处理。异步处理就需要考虑线程安全和队列的问题,详情可以参考JDK的Message Queue。
5 观察者模式的扩展
5.1 Java中的观察者模式
Java本身支持了观察者模式,并且提供了可用于扩展的父类:java.util.Observable
实现类和java.util.Observer
接口。
- 被观察者
被观察者,继承java.util.Observable
:
@Slf4j
public class ConcreteSubject extends Observable {
/**
* 具体被观察者独特的业务逻辑
*/
public void uniqueMethod() {
//具体业务
log.info("具体业务逻辑{}.uniqueMethod", this.getClass().getSimpleName());
}
/**
* 业务逻辑
*/
public void doSomething() {
log.info("被观察者{}执行业务逻辑...", this.getClass().getSimpleName());
this.setChanged();
this.notifyObservers();
}
}
- 观察者
观察者实现java.util.Observer
接口:
@Slf4j
public class ConcreteObserver implements Observer {
/**
* 观察者名字
*/
private String name;
public ConcreteObserver(String name) {
this.name = name;
}
@Override
public void update(Observable o, Object arg) {
log.info("观察者[{}]观察到变化。{}.update", this.name, this.getClass().getSimpleName());
ConcreteSubject observer= (ConcreteSubject) o;
observer.uniqueMethod();
}
}
- 场景类
场景应用观察者和被观察者
@Slf4j
public class Client {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject();
ConcreteObserver observer1 = new ConcreteObserver("OBSERVER1");
ConcreteObserver observer2 = new ConcreteObserver("OBSERVER2");
subject.addObserver(observer1);
subject.addObserver(observer2);
subject.doSomething();
}
}
运行结果:
23:04:10.055 [main] INFO com.idear.design.pattern.observer.jdk.ConcreteSubject - 被观察者ConcreteSubject执行业务逻辑...
23:04:10.055 [main] INFO com.idear.design.pattern.observer.jdk.ConcreteObserver - 观察者[OBSERVER2]观察到变化。ConcreteObserver.update
23:04:10.055 [main] INFO com.idear.design.pattern.observer.jdk.ConcreteSubject - 具体业务逻辑ConcreteSubject.uniqueMethod
23:04:10.055 [main] INFO com.idear.design.pattern.observer.jdk.ConcreteObserver - 观察者[OBSERVER1]观察到变化。ConcreteObserver.update
23:04:10.055 [main] INFO com.idear.design.pattern.observer.jdk.ConcreteSubject - 具体业务逻辑ConcreteSubject.uniqueMethod
5.2 订阅-发布模型
EJB3(Enterprise JavaBean)是一个非常优秀的框架,比起前两代,还算轻量级。EJB3中有3个类型的Bean:
- Session Bean
- Entity Bean
- MessageDriven Bean。
MessageDriven Bean(MDB),消息驱动Bean:消息的发布者(Provider)发布一个消息(一个消息驱动Bean),通过EJB容器(一般是Message Queue消息队列)通知订阅者做出相应。原理上很简单,类似于观察者模式的升级版。
6 项目中真实的观察者模式
一般在真实的项目中,系统设计时会对观察者模式进行改造或改装,主要体现在以下3个方面。
- 观察者和被观察者之间的消息沟通
被观察者状态改变会触发观察者的一个行为,同时传递一个消息给观察者。在实际中一般做法是:观察者中的update方法接受两个参数,一个是被观察者,一个是DTO(Data Transfer Object,数据传输对象)。DTO一般是一个纯粹的JavaBean,由被观察者生成,由观察者消费。如果考虑到远程传输,可以通过JSON或XML格式传递。 - 观察者响应方式
观察者一般逻辑复杂,需要接受被观察者传递过来的信息并进行处理。当一个观察者——多个被观察者时,性能就需要进行考虑。如果观察者来不及响应,被观察者执行时间也会被拉长。这种情况有两种处理办法:
- 异步架构
采用多线程技术,被观察者或者观察者都可以启动线程,可以明显地提高系统性能。 - 同步架构
采用缓存技术,保证有足够的资源,随时响应。但是,代价是开发难度大,需要进行充分的压力测试。
- 被观察者尽量自己做主
被观察者具体实现根据业务逻辑决定是否要通知观察者,而不是消息到达观察者时才判断是否需要消费,导致浪费网络资源,加重消费者的处理逻辑和处理压力。