设计模式——观察者模式

观察者模式是在开发过程中用得比较多的一种模式。根据应用场景的不同,观察者模式有不同的实现方式:同步阻塞式,异步非阻塞式,甚至可以利用消息队列实现跨进程的观察者模式。

1. 观察者模式的原理

观察者模式也被称为发布订阅模式 (Publish-Subscribe Design Pattern),Design Patterns 一书中对它的描述如下:

Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

2. 观察者模式的实现

观察者模式根据应用场景的不同有多种不同的实现方式,首先我们来看最经典的一种实现方式——同步阻塞式。

2.1 同步阻塞式

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
public interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers(Message message);
}

public interface Observer {
void update(Message message);
}

public class SubjectImpl implements Subject {
private List<Observer> observers = new ArrayList<>();

@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}

@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}

@Override
public void notifyObservers(Message message) {
for(Observer observer : observers) {
observer.update(message);
}
}
}

同步阻塞式的代码非常简单,我就不做过多的解释了。同步阻塞式的观察者模式有一个很明显的弊端:Subject 必须遍历所有的 Observer,并依次调用 Observer.update() 方法,等所有的 Observer.update() 方法结束后,Subject.notifyObservers() 方法才会返回。如果某个 Observer 的 update() 方法比较耗时,又或者 Subject 注册了很多 Observer,那么 notifyObservers() 的性能将十分堪忧。异步非阻塞的实现方式可以很好地解决这个问题。

2.2 异步非阻塞式

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
public interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers(Message message);
}

public interface Observer {
void update(Message message);
}

public class SubjectImpl implements Subject {
private List<Observer> observers = new ArrayList<>();
private Executor executor;

@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}

@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}

@Override
public void notifyObservers(Message message) {
for(Observer observer : observers) {
executor.execute(() -> observer.update(message));
}
}
}

异步非阻塞方式和同步阻塞方式的代码非常相似,唯一的不同就是我们将通知 Observer 的任务下发给了 Executor。

2.3 跨进程方式

不管是同步阻塞方式,还是异步非阻塞方式,Subject 和 Observer 都位于同一个进程内。不过,我们可以利用 RPC 或者是消息队列实现跨进程的观察者模式。基于 RPC 的方式比较直接,我们只需要在 Observer.update() 方法中调用 RPC 接口即可。

基于消息队列的方式相对来说会更加优雅,也更加常见。它将 Subject 和 Observer 解耦地更加彻底:Subject 完全感知不到 Observer,它只需要向消息队列中添加消息;Observer 也完全感知不到 Subject,它只需要从消息队列中读取消息。

3. 观察者模式的应用

观察者模式的应用场景非常广泛,它可以应用于代码的解耦,又或者是架构的设计,甚至是一些产品的设计都有观察者模式的影子,比如:邮件订阅,RSS Feeds…

接下来,我们以两个具体的例子来讲讲观察者模式的应用。

3.1 注册通知

在一些金融系统中,我们往往会有这样的需求:用户注册成功之后,我们会赠送用户一些体验金。这个需求不难实现,大致逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
public class UserController {
private UserService userService;
private PromotionService promotionService;

public Long register(String telephone, String password) {
// authentication...
long userId = userService.register(telephone, password);
promotionService.issueExperienceCash(userId);
return userId;
}
}

UserController.register() 接口做了两件事情:注册和发放体验金。这违反了单一职责原则,但是如果没有扩展和修改的需求,这样做也是无可厚非的。

但是需求往往是变动的,比如:现在不发体验金了,改发优惠券,并且还需要发送一封"欢迎新用户"的站内信。随着需求越来越多,UserController.register() 的逻辑会越来越复杂,代码的可读性和可维护性会越来越差…

这时候,观察者模式就可以派上用场了。我们可以利用观察者模式对代码进行重构:

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
public interface RegisterObserver {
void afterRegister(long userId);
}

public class PromotionRegisterObserver implements RegisterObserver {
private PromotionService promotionService;

@Override
public void afterRegister(long userId) {
promotionService.issueExperienceCash(userId);
}
}

public class NotificationRegisterObserver implements RegisterObserver {
private NotificationService notificationService;

@Override
public void afterRegister(long userId) {
String message = ...
notificatinService.sendInboxMessage(userId, message);
}
}

public class UserController {
private UserService userService;
private List<RegisterObserver> observers;
private Executor executor;

public void addObserver(RegisterObserver observer) {
this.observers.add(observer);
}

public Long register(String telephone, String password) {
// authentication...
long userId = userService.register(telephone, password);
for (RegisterObserver observer : observers) {
executor.execute(() -> observer.afterRegister(userId));
}
return userId;
}
}

代码重构之后,如果有新的需求,比如:用户注册成功之后,需要推送用户信息给大数据征信系统。UserController.register() 接口是不需要发生任何改动的,我们只需要添加一个 RegisterObserver 的实现类,并且将它注册到 UserController 中即可。

小结

在这篇文章中,我们主要讲了观察者模式的原理,它的实现方式,以及通过一个具体的案例展示了如何应用观察者模式去重构已有的代码。

0%