观察者模式

前言

观察者模式是一个非常重要的模式,虽然在实际工作中可能很少去直接自己去构造一个观察者模式,并不像单例模式、工厂模式、模板方法模式这样。直接硬编码写出来。但是我们肯定会用到它的补充形式的。比如发布-订阅、监听器、Spring的事件驱动模式。因为毕竟观察者模式直接使用有时候不能达到我们的解耦要求。所以才有了上面的这些常用的东西。

为什么使用观察者模式

在软件系统中,当一个对象行为依赖另外一个对象的状态时。如果不是使用观察者模式的通用结构。则只能在另一个线程中不停的监听查看对象所需要的依赖。在一个复杂的系统中,可能会开启很多线程来实现这一功能,会严重浪费系统的性能。观察者模式的意义也在于此,它可以在单线程中,使某一对象,及时得知自身所依赖的状态的变化,根据其变化做出来相应的动作。

简单的观察者模式

观察者模式主要分为四个角色

角 色 作 用
主题接口 指被观察者对象。当其状态发生改变,它会将这个变化通知给观察者,它维护了观察者所需要依赖的状态
具体主题 具体主题实现了主题接口
观察者接口 观察者接口定义了观察者的基本方法。当依赖状态发生改变时,主题接口就会调用观察者模式的方法
具体观察者 具体观察者实现了观察者接口

我们来看一下代码的基本实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 观察者接口
public interface IObserver{
void update(Event event);
}
// 主题接口
public interface ISubject {
void addObserver(IObserver server); // 新增观察者
void delObserver(IObserver server); // 删除观察者
void inform(); // 通知观察者
}
// 主题接口实现
public class SubjectImpl implements ISubject{
Vector<IObserver> observers = new Vector<>();
public void addObserver(IObserver server){
observers.add(server);
}
public void delObserver(IObserver server){
observers.removeElement(server);
}
public void inform(){
Event event = new Event();
for(IObserver server : observers){
server.update(evt);
}
}
}
// 具体观察者
public class ObserverImpl implements IObserver{
public void update(Event event){
log.info("接受到消息!");
}
}

上面这段代码就是一个简单的实现。可以看出来。其实不复杂。就是简单的状态在需要通知的时候主题接口调用一下通知接口即可。

JDK中的观察者模式

JDK已经为我们准备了一套现有的观察者模式的实现,我们可以直接使用它即可,在java.util包中,包括Observable和Observer接口

在看一些代码的实现,这里我们以书本订阅的模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//  订阅书的人
@Slf4j
public class PaperListener implements Observer {
@Override
public void update(Observable o, Object arg) {
log.info("收到书了:{}",arg);
}
}
public class Bookstore extends Observable {

public static void main(String[] args) throws InterruptedException {

PaperListener paperListener1 = new PaperListener();
PaperListener paperListener2 = new PaperListener();
Bookstore bookstore = new Bookstore();
bookstore.addObserver(paperListener1); // 订阅人1
bookstore.addObserver(paperListener2); // 订阅人2

Thread.sleep(1000);
// 杂志到了
bookstore.setChanged();
bookstore.notifyObservers("西游记");
bookstore.setChanged();
bookstore.notifyObservers("三国演义");
}
}

执行结果

1
2
3
4
21:02:44.360 [main] INFO com.document.observer.PaperListener - 收到书了:西游记
21:02:44.384 [main] INFO com.document.observer.PaperListener - 收到书了:西游记
21:02:44.384 [main] INFO com.document.observer.PaperListener - 收到书了:三国演义
21:02:44.385 [main] INFO com.document.observer.PaperListener - 收到书了:三国演义

其实当我们查看Observable的源码,也就知道了其实里面和简单的观察者模式是类似的。不过它为了公用性和并发性做了一些改造。我们每次都要去改变一个change变量才能去通知。实际使用中也要继承Observable来做的。有兴趣的可以去自己使用下

不过看起来还有缺点,比如它是需要继承Class来实现。而Java只能继承一个类。而且它的标记change只有一个。不能做到组合标记

发布-订阅和观察者模式

前面说了发布订阅可以说是观察者模式的一个补充,因为观察者模式的主题和订阅至于两层,里面的订阅者的维护工作是交给主题自己本身来做的,耦合度不太好。所有发布订阅相当于把他拆分成三个成员:主题、订阅者、维护队列者。这样主题和订阅者就做到完全解耦的工作。队列的维护交给一个实际的类或者服务来做。

所有发布-订阅模式比观察者模式有更好的耦合度,但是在设计思想上是统一的

当然如果你把这个模式在扩展下,比如把维护队列的对象扩展成一个服务,那么就是一个简单的消息队列的原型

Spring中的事件驱动模式

这里讲一下Spring中的ApplicationListener,因为它也是观察者模式的一个实现(毕竟挖了坑,要填)。

先看下简单的使用代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 事件
public class MyEvent extends ApplicationEvent {
public MyEvent(Object source) {
super(source);
}
}
// 监听器 注意要加入Bean,Spring会在启动中,将其加入发布广播器中
@Slf4j
@Component
public class MyListener implements ApplicationListener<MyEvent> {
@Override
public void onApplicationEvent(MyEvent event) {
log.info("收到消息了:{}",event);
}
}
// 开始发布消息
@SpringBootApplication
public class WebBaseApplication {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(WebBaseApplication.class, args);
run.publishEvent(new MyEvent(new Object()));
}
}

输出日志:收到消息了:com.duteliang.webbase.listener.MyEvent[source=java.lang.Object@3d4e405e]

其实也是和观察者模式的设计思想是类似的。但是做了一些变化。看下发布的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
Assert.notNull(event, "Event must not be null");
// Decorate event as an ApplicationEvent if necessary
ApplicationEvent applicationEvent;
if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent) event;
}
else {
applicationEvent = new PayloadApplicationEvent<>(this, event);
if (eventType == null) {
eventType = ((PayloadApplicationEvent<?>) applicationEvent).getResolvableType();
}
}
// Multicast right now if possible - or lazily once the multicaster is initialized
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
} else {
// 这里关键代码,获取广播器,发布消息
getApplicationEventMulticaster().multicastEvent(applicationEvent, eventType);
}
// Publish event via parent context as well...
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext) this.parent).publishEvent(event, eventType);
}
else {
this.parent.publishEvent(event);
}
}
}

跟下去看下广播器代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 默认使用的SimpleApplicationEventMulticaster广播器
class SimpleApplicationEventMulticaster implements ApplicationEventMulticaster{
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) { // 如果有Executor 走异步调用
executor.execute(() -> invokeListener(listener, event));
}
else { // 同步
invokeListener(listener, event);
}
}
}
// invokeListener 实际是调用这个 doInvokeListener方法,我省略了一个
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
listener.onApplicationEvent(event);
}
catch (ClassCastException ex) {
// 无关代码忽略
}
}
}

看到这里其实广播器SimpleApplicationEventMulticaster就相等于主题实现。从接口ApplicationEventMulticaster就可以看到有管理观察者的方法。
ApplicationListener相当于观察者接口。 Spring引进了Event的概念,使其功能更加丰富了而已,调用观察者的时候根据事件类型来通知。

也可以选择异步通知还是同步通知,以及是否忽略错误等功能。

比如@EventListener就是为此而建立的,可以异步监听消息。更多的功能可以自己去查下资料了!

1
2
3
4
5
6
7
@Component
public class OrderEventListener {
@EventListener
public void handleOrderEvent(OrderEvent event) {
System.out.println("我监听到了handleOrderEvent发布的message为:" + event.getMsg());
}
}

在开发中如果有需要使用观察者模式,可以推荐Guava 中的EventBus,对其的扩展非常丰富。可以去尝试一下