1、前言
观察者模式:当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)又叫发布-订阅模式(Publish/Subscribe)。比如,当一个对象被修改时,则会自动通知依赖它的对象。观察者模式属于行为型模式。
2、应用场景及说明
主要用来解决一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。
从游戏角度出发,就是从被动定时拉取,变成主动推送的方式。比如红色警戒中【尤里】控制对方工具,只有【尤里】时候对方工具才能解除控制。这里解除控制的时候,就只直接通知对方工具解除控制。否则就可能出现对方工具定时检测【尤里】的是否存活来判断是否解除控制。
从业务来讲,更多的应用在消息的推动上,比如委托方下单后,需要通知物流公司和库存的库管有一辆车即将进入你们系统。只需要物流系统和库存系统订阅委托方下单的功能,委托方下单后就会直接通知物流和库存,又比如车辆信息修改,也可以直接通知物流库存。
3、角色说明
从上面我们可以看到,这里面包含了两大类(主题和观察者)一共四个角色:
(1)Subject:抽象主题,他把所有观察者对象保存在一个集合里,可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
(2)ConcreteSubject:具体主题,该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
(3)Observer:抽象观察者,是观察者者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
(4)ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。
其中我们主题(Subject)就是车辆信息变化,具体的主题(ConcreteSubject)就是委托方下单业务,具体的观察者(ConcrereObserver)就是物流业务和库存业务
4、代码案例
UML类图:
抽象的主题:
/**
* 主题
*
* @author: ws
* @date: 2020/9/21 13:32
*/
public interface Subject {
/**
* 添加观察者
* @param observer
*/
void addObserver(Observer observer);
/**
* 删除观察者
* @param observer
*/
void removeObserver(Observer observer);
/**
* 通知观察者
*/
void notifyObservers();
}
抽象的观察者:
/**
* 观察者
*
* @author: ws
* @date: 2020/9/21 13:40
*/
public interface Observer {
void update();
}
具体的主题-车辆信息变更:
/**
* 车辆更新/变化主题
*
* @author: ws
* @date: 2020/9/21 13:34
*/
public class VehicleUpdateSubject implements Subject{
// 线程安全的集合
private Vector<Observer> vectors = new Vector<>();
@Override
public void addObserver(Observer observer) {
vectors.add(observer);
}
@Override
public void removeObserver(Observer observer) {
vectors.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : vectors) {
observer.update();
}
}
public void vehicleUpdate(String msg){
System.out.println("*******"+ msg +"********");
this.notifyObservers();
}
}
观察者-物流系统:
/**
* 观察者-物流
*
* @author: ws
* @date: 2020/9/21 13:45
*/
public class WlObservable implements Observer{
private Subject subject;
private String name;
public WlObservable(Subject subject, String name) {
this.subject = subject;
this.name = name;
subject.addObserver(this);
}
@Override
public void update() {
System.out.println("观察者:【"+name+"】接受到了通知!");
}
}
观察者-库存系统:
/**
* 观察者-仓库
*
* @author: ws
* @date: 2020/9/21 13:49
*/
public class StoreObservable implements Observer{
private Subject subject;
private String name;
public StoreObservable(Subject subject, String name) {
this.subject = subject;
this.name = name;
subject.addObserver(this);
}
@Override
public void update() {
System.out.println("观察者:"+name+"接受到了通知!");
}
}
客户端测试:
/**
* 客户端测试
*
* @author: ws
* @date: 2020/9/21 13:51
*/
public class PatternClient {
public static void main(String[] args) {
// 设置具体的主题
VehicleUpdateSubject subject = new VehicleUpdateSubject();
// 添加观察者(订阅主题)
Observer wlObserver = new WlObservable(subject, "物流系统");
Observer storeObserver = new WlObservable(subject, "库存系统");
// 车辆发生改变
subject.vehicleUpdate("委托方下单-新增车辆");
}
}
执行结果:
5、分析观察者模式
不管是公司业务还是游戏中的尤里,使用观察者模式除了通知类的模式之外,这其实也是一种推拉模型。
- 推模型就是主动推送主题消息,不管观察者是否真的需要,只要订阅就会直接推送;
- 拉模型就是观察者根据自己需要被动拉取主题中的信息,但是并不知道主题中的信息什么时候更新。
6、观察者模式 VS 发布订阅模式(Pub-Sub Pattern)
观察者模式也叫发布-订阅模式,但是又区别我们常说的发布订阅。现在所说的发布订阅已经独立于观察者模式,成为另外一种不同的设计模式,以消息中间件最为常见。
观察者模式里面,update方法所在的实例对象,就是被观察者(Subject,或者叫Observable),它只需维护一套观察者(Observer)的集合,这些Observer实现相同的接口,Subject有改变时,直接通知所有观察者,至于观察者是否需要,主题并不关心,只要注册了,就会收到改变的信息。
发布订阅模式中发布者的消息发送者不会将消息直接发送给订阅者,而是通过第三个组件,该组件称为消息代理或调度中心或中间件,它维持着发布者和订阅者之间的联系,过滤所有发布者传入的消息并相应地分发它们给订阅者。
7、观察者模式的延伸
- 发布订阅:常用的消息中间件(消息队列),redis中的发布订阅等。其中以消息中间件最为常用,用来异步处理分布式系统中的消息通知问题,以此来解耦。
- Spring的事件机制(监听器)【ApplicationEvent , ApplicationListener】:Spring框架中事件机制(模型)非常多,比如各种监听器,第三方框架整合Spring时的事件触发(阿波罗)等
8、事件驱动模型案例
改造观察者模式,使用Spring事件机制模型。Spring事件机制中主要包含三要素:
- 事件源(ApplicationEvent)
- 监听器(ApplicationListener)
- 发布事件者(ApplicationEventPublisher以及ApplicationEventMulticaster)
简要说明:
ApplicationEvent事件源是我们自定义的,ApplicationListener监听器是注册在Spring容器中的。ApplicationEventPublisher中ApplicationEventMulticaster类具备发布功能,当事件发布者发布事件之后,监听器监听到事件源就会执行相应的逻辑。
其中ApplicationListener相当于观察者模式中的观察者(Observe)。事件源相当于主题(Subject)。区别在于事件不会主动通知观察者,而是通过监听器这个中间介质来将两者关联起来的。
事件发布者我们可以自定义,也可以不定义。直接使用spring中的ApplicationContext来发布事件,该类继承了ApplicationEventPublisher类,具备发布事件的功能。
定义事件:
/**
* 委托方下单事件
*
* @author: ws
* @date: 2020/9/21 15:12
*/
public class BosOrderEvent extends ApplicationContextEvent {
private String eventName;
private String msg;
public BosOrderEvent(ApplicationContext source, String eventName, String msg) {
super(source);
this.eventName = eventName;
this.msg = msg;
}
public String getEventName() {
return eventName;
}
public void setEventName(String eventName) {
this.eventName = eventName;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
定义物流系统监听器:
/**
* 物流监听
*
* @author: ws
* @date: 2020/9/21 15:20
*/
public class WlListener implements ApplicationListener<BosOrderEvent>{
@Override
public void onApplicationEvent(BosOrderEvent event) {
System.out.println("物流系统监控到:【"+event.getEventName()+"】事件被触发,通知消息为:"+event.getMsg());
}
}
定义库存系统监听器:
/**
* 库存监听器
*
* @author: ws
* @date: 2020/9/21 15:20
*/
public class StockListener implements ApplicationListener<BosOrderEvent>{
@Override
public void onApplicationEvent(BosOrderEvent event) {
System.out.println("库存系统监控到:【"+event.getEventName()+"】事件被触发,通知消息为:"+event.getMsg());
}
}
事件发布类:
/**
* 事件发布类
*
* @author: ws
* @date: 2020/9/21 15:26
*/
public class BosOrderPublish implements ApplicationEventPublisherAware {
// 获取Spring中的发布事件对象
private ApplicationEventPublisher applicationEventPublisher;
@Override
public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
this.applicationEventPublisher = applicationEventPublisher;
}
public void publishEvent(ApplicationEvent event){
System.out.println("发布事件被调用。。。。。");
applicationEventPublisher.publishEvent(event);
}
}
配置类:
/**
* 委托方下单配置类
*
* @author: ws
* @date: 2020/9/21 15:35
*/
@Configuration
public class BosOrderConfig {
@Bean
public WlListener wlListener(){
return new WlListener();
}
@Bean
public StockListener stockListener(){
return new StockListener();
}
@Bean
public BosOrderPublish bosOrderPublish(){
return new BosOrderPublish();
}
}
测试类:
public class BosOrderTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(BosOrderConfig.class);
BosOrderEvent bosOrderEvent = new BosOrderEvent(context, "bos下单", "车辆更新");
// context.publishEvent(bosOrderEvent);
BosOrderPublish bosOrderPublish = (BosOrderPublish)context.getBean("bosOrderPublish");
bosOrderPublish.publishEvent(bosOrderEvent);
}
}
运行结果:
UML类图: