设计模式之观察者模式实战

本文最后更新于:2021年6月15日 晚上

本文介绍观察者模式在项目中的实际应用。

类型:行为型模式

意图:一对多关系依赖的多个对象,当一个对象状态发生改变,所有依赖的对象都可以得到通知并自动更新

主要解决:降低对象间的关联依赖性

观察者模式也称为发布订阅模式,监听器模式。

设计模式系列文章目录

角色

抽象主题(Subject)角色:抽象主题角色提供维护一个观察者对象聚集的操作方法,对聚集的增加、删除等。
具体主题(ConcreteSubject)角色:将有关状态存入具体的观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色负责实现抽象主题中聚集的管理方法。
抽象观察者(Observer)角色:为具体观察者提供一个更新接口。
具体观察者(ConcreteObserver)角色:存储与主题相关的自洽状态,实现抽象观察者提供的更新接口。

UML

Java 提供观察者模式的支持

一般在真实项目之中,不会完全手动实现一个观察者模式,因为在 JAVA 语言的 java.util 库里面,已经提供了一个 Observable 类以及一个 Observer 接口,构成 JAVA 语言对观察者模式的支持。直接使用提供的 util 即可。

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 ConcreteObserver implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("接收到更新");
}
}

public class ConcreteSubject extends Observable {

public void change() {
setChanged();
this.notifyObservers();
}
}

public class Test {
public static void main(String[] args) {
ConcreteObserver concreteObserver = new ConcreteObserver();
ConcreteSubject subject = new ConcreteSubject();
subject.addObserver(concreteObserver);
subject.change();
}
}

实战

除了 Java 自身 提供的观察者模式支持外,Guava 也基于观察者模式实现的 生产/消费模型,在使用上,比 Observable 相对简单,如果需要订阅消息只需要在方法上添加 @Subscribe 注解即可。使用 EventBus 的 post 方法分发事件给消费者。

本文以用户注册为例,当用户注册完成后,之后可能会有一系列的耗时操作,比如发送消息通知,同步数据到缓存等操作。为了给用户提供好的体验,这里使用 EventBus 来进行异步化。

定义抽象主题

1
2
3
4
5
6
7
8
9
public abstract class AbstractProducer<T> {
public static final AsyncEventBus eventBus = new AsyncEventBus("_event_async_", Executors.newFixedThreadPool(4));

public void registerAsyncEvent(EventConsumer consumer) {
eventBus.register(consumer);
}

public abstract void post(T event);
}

定义抽象观察者

1
2
3
public interface EventConsumer<T> {
void consume(T event);
}

封装 Event 事件对象

Event 事件对象用于事件宣发时参数传递使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
@Data
public class UserRegisterEvent {
private UserDto userDto;
}

@Data
@ToString
@AllArgsConstructor
public class UserDto {
private Long id;
private String name;
private LocalDateTime registerTime;
}

定义具体的主题与观察者对象

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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 主题对象,提供注册主题方法
@Component
public class UserRegisterProducer extends AbstractProducer<UserRegisterEvent> {

@Override
public void post(UserRegisterEvent event) {
eventBus.post(event);
}
}

// 观察者对象,监听主题事件
@Component
public class UserRegisterNotifyConsumer implements EventConsumer<UserRegisterEvent>,
InitializingBean {

@Resource
private UserRegisterProducer userRegisterProducer;

@Override
@Subscribe // 监听事件
public void consume(UserRegisterEvent event) {
System.out.println("接收到用户注册事件,开始推送通知");
System.out.println(event);
System.out.println("接收到用户注册事件,通知推送完毕");
}

@Override
public void afterPropertiesSet() {
userRegisterProducer.registerAsyncEvent(this);
}
}

// 观察者对象,监听主题事件
@Component
public class UserRegisterSyncCacheConsumer implements EventConsumer<UserRegisterEvent>,
InitializingBean {

@Resource
private UserRegisterProducer userRegisterProducer;

@Override
public void afterPropertiesSet() {
userRegisterProducer.registerAsyncEvent(this);
}

@Override
@Subscribe // 监听事件
public void consume(UserRegisterEvent event) {
System.out.println("接收到用户注册事件,开始同步 Cache");
System.out.println(event.getUserDto());
System.out.println("接收到用户注册事件,同步 Cache 完毕");
}
}

注意,观察者对象需要在类进行实例化的时候,进行注册事件,所以实现了 InitializingBean 接口,监听事件消息,需要在监听方法上添加 @Subscribe 注解。

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserRegisterProducerTest {

@Resource
private UserRegisterProducer userRegisterProducer;
@Test
public void post() {
UserRegisterEvent event = new UserRegisterEvent();
event.setUserDto(new UserDto(1L, "张三", LocalDateTime.now()));
userRegisterProducer.post(event);
}
}

结果输出:

1
2
3
4
5
6
接收到用户注册事件,开始推送通知
接收到用户注册事件,开始同步 Cache
UserDto(id=1, name=张三, registerTime=2020-03-05T22:12:26.386)
接收到用户注册事件,同步 Cache 完毕
UserRegisterEvent(userDto=UserDto(id=1, name=张三, registerTime=2020-03-05T22:12:26.386))
接收到用户注册事件,通知推送完毕

因为观察者模式是无法控制消费顺序的,可能每次的输出结果都是不一致的。

示例代码

参考