观察者设计模式
观察者设计模式中存在三种对象,分别是事件源、事件和监听器。其中事件源相当于被观察者,被观察者会触发事件,而监听器会在触发的事件中通知观察者。
特点
- 持有监听的观察者的引用
- 支持增加和删除观察者
- 主题状态改变,通知观察者
自己实现的观察者模式
监听器对象(观察者)
只需要提供一个接口
public interface GameListener {
public void update(GameEvent gameEvent);
}
想要监听的对象只需要实现此接口就可以成为监听器,例如下面这个Person对象就是一个观察者。
public class Person implements GameListener{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person(String name) {
this.name = name;
}
public void update(GameEvent gameEvent){
if(gameEvent.getType() == 1){
System.out.println(name+"开始休息");
}
}
}
事件
事件对象,在被观察者中会触发的事件
public class GameEvent {
private String context;
private int type;
public String getContext() {
return context;
}
public void setContext(String context) {
this.context = context;
}
public int getType() {
return type;
}
public void setType(int type) {
this.type = type;
}
}
被观察者
这里就是核心,关键就是被观察者保留观察者的引用,这样触发事件的时候就可以通知观察者,这样就可以解放观察者的观察所带来的资源浪费。
public class PlayGame implements Runnable{
// 监听器
private List<GameListener> gameListeners;
public void addPlayer(GameListener gameListener){
gameListeners.add(gameListener);
}
public PlayGame() {
this.gameListeners = new ArrayList<>();
}
@Override
public void run() {
System.out.println("开始打游戏");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("需要休息一下");
// 触发事件
GameEvent gameEvent = new GameEvent();
gameEvent.setType(1);
// 循环遍历调用观察者方法
for (GameListener gameListener : gameListeners) {
gameListener.update(gameEvent);
}
}
}
测试
public static void main(String[] args) {
// 创建监听者
Person zdd = new Person("zdd");
Person pipi = new Person("pipi");
// 创建被观察者
PlayGame playGame = new PlayGame();
// 添加监听器
playGame.addPlayer(zdd);
playGame.addPlayer(pipi);
Thread thread = new Thread(playGame);
thread.run();
}
JDK实现的观察者模式
JDK的观察者模式的实现依赖于下面两个类和接口
- Observable:类,被观察者需要继承此类
- Observer:接口,观察者需要实现此接口,在update方法中实现事件触发的方法
而且JDK实现的观察者只需要我们手动创建两个对象,一个是被观察者,一个是观察者。
被观察者
注意如果需要触发事件调用setChanged
和notifyObservers
方法,而且任何一个方法没有调用都会触发事件失败。
public class PlayGame extends Observable implements Runnable{
@Override
public void run() {
System.out.println("开始打游戏");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("需要休息一下");
// 触发事件
// 改变状态
setChanged();
// 唤醒观察者
// 可以传递参数,在观察者中的update方法中可以拿到
notifyObservers();
}
}
观察者
事件触发会调用update方法
public class Person implements Observer {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Person(String name) {
this.name = name;
}
/**
* 第一个参数是事件源,也就是被观察者
* 第二个参数是notifyObservers方法传递进来的参数
* @param o
* @param arg
*/
@Override
public void update(Observable o, Object arg) {
System.out.println(name+"开始休息");
System.out.println(o);
System.out.println(arg);
}
}
测试
public static void main(String[] args) {
// 创建监听者
Person zdd = new Person("zdd");
Person pipi = new Person("pipi");
// 创建被观察者
PlayGame playGame = new PlayGame();
// 添加监听器
playGame.addObserver(zdd);
playGame.addObserver(pipi);
// 删除单个监听器
// playGame.deleteObserver(zdd);
// 删除所有监听器
// playGame.deleteObservers();
Thread thread = new Thread(playGame);
thread.run();
}
优点
JDK的观察者模式各个组件之间的耦合度很低,而且Observable提供了很多API,可以很方便完成触发事件,更改监听器等等操作。
Spring的观察者模式
spring框架中同样存在观察者模式,而且我们得知道spring中的被观察对象自然是spring本身。而且spring中存在很多可以触发的事件,我们可以通过扩展这些的事件来间接使用它,不过在此之前我们需要先了解一下spring中的事件。
spring的事件总览
spring中最重要的事件类就是ApplicationEvent,它是spring实现的事件父类,它有两个重要子类ApplicationContextEvent和RequestHandledEvent。
而ApplicationContextEvent又有几个重要的子类:
- ContextStartedEvent:在上下文启动时被启动
- ContextStoppedEvent:在上下文停止时被启动
- ContextRefreshedEvent:当上下文被刷新时产生
- ContextClosedEvent:在上下文关闭时产生
它们的继承关系图如下
RequestHandledEvent
这个事件与request请求相关联,当在ApplicationContext中处理请求时,它们被引发。不过这里不会对这类事件进行分析。
ApplicationContextEvent
它与应用程序上下文相关联,它们应用于由org.springframework.context.ApplicationContext引发的事件(其构造函数传入的是ApplicationContext
类型的参数)。
这样,我们就可以直接通过应用程序上下文的生命周期来得到所发生的事件:ContextStartedEvent
在上下文启动时被启动,当它停止时启动ContextStoppedEvent
,当上下文被刷新时产生ContextRefreshedEvent
,最后在上下文关闭时产生ContextClosedEvent
。
监听ApplicationContextEvent的子类事件
我们可以直接监听ApplicationContextEvent中任意一个子类,这里演示的是监听ContextStartedEvent事件,只需要创建一个监听对象实现ApplicationListener<泛型>,在泛型中写上需要监听的事件。
@Component
public class MyMonitor implements ApplicationListener<ContextStartedEvent> {
@Override
public void onApplicationEvent(ContextStartedEvent event) {
System.out.println("自定义监听");
}
}
ContextStartedEvent事件需要执行上下文对象的start方法
监听自定义事件
我们同样可以直接继承ApplicationEvent创建一个自定义的事件。
不过需要注意的是我们创建的事件想要触发也必须得我们自己去主动去触发,这里我通过一个bean的方法触发的事件。
- 自定义事件
public class MyContextEvent extends ApplicationEvent {
/**
* Create a new ApplicationEvent.
*
* @param source the object on which the event initially occurred (never {@code null})
*/
public MyContextEvent(Object source) {
super(source);
}
}
- 监听对象
@Component
public class EventMonitor implements ApplicationListener<MyContextEvent> {
@Override
public void onApplicationEvent(MyContextEvent event) {
System.out.println("自定义事件监听");
}
}
- 触发对象
@Component
public class Trigger {
@Autowired
ApplicationContext applicationContext;
public void triggerEvent(){
System.out.println("triggerEvent");
// 触发事件
applicationContext.publishEvent(new MyContextEvent(applicationContext));
}
}
- 测试
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
Trigger trigger = ac.getBean(Trigger.class);
// 触发事件
trigger.triggerEvent();
}