今天要跟大家聊的是观察者模式(Observer Pattern),观察者模式非常容易理解,好比说微博,用户关注了明星成为其粉丝,每当明星发布微博的时候总是会给用户发送一条消息推送通知。
在这个例子中,用户就是观察者,所有关注明星的人也是观察者,而这个明星就是被观察者,每当被观察者某个状态发生变化(如发布微博),就会给所有的观察者发送通知。
1. 定义
观察者模式是一种行为型设计模式,它的定义是:“定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。”
2. 类图
在观察者模式中,有这样几种角色:
- 被观察者,主题角色(Subject):定义被观察者必须实现的职责,包括增加、删除、通知观察者
- 观察者(Observer):观察者接收到主题角色发出的通知消息后,即执行响应操作
- 具体的被观察者(Concrete Subject):定义被观察者自己的业务逻辑,同时也可以指定对特定的事件进行通知
- 具体的观察者(Concrete Observer):每个观察者在接收到通知消息后的处理可能是不同的,具体的观察者可以自定义响应通知逻辑
观察者模式的类图如下:
观察者模式有多种实现方式:
- 同步阻塞形式:观察者基于同一个线程来执行
- 异步非阻塞形式:每个观察者会有不同的线程来执行,通常可以使用线程池来实现
- 进程间形式:
- 同步:基于 RPC 接口进行同步 RPC 接口调用
- 异步:基于 MQ 消息中间件进行消息通知,主题角色将对观察者角色的响应结果无感知
事实上,对于观察者模式应用最多的场景就是上面所描述四种实现方式的第四种:进程间异步形式。通常在项目中开发人员会引入 MQ 消息队列中间件,以此来将主题角色与观察者角色进行接耦,既能够提高主题角色本身业务的处理效率,降低响应时间,提高并发量,同时也可以将主题角色与观察者角色进行接耦,即便观察者角色响应逻辑处理失败了,也不影响主题角色本身的业务逻辑的执行。
3. 示例
每一个C端平台的兴起,最初都离不开烧钱,吸引流量,毕竟在这个流量为王的时代,有人在,就有玩下去的本钱,谁掌握了流量密码,谁就是这个行业的老大。
例如近些年来在程序员行业中评价颇高的极客时间,为了吸引新用户注册,它推出了“注册即享88元红包”的新人专享福利(当然这红包不是现金红包而是优惠券)。
下面就以新用户注册C端平台,平台赠送用户新人专属福利的场景来作为观察者模式的示例。
首先定义抽象被观察者以及观察者接口,被观察者需要包涵管理观察者的功能,并且还能通知观察者。观察者接口只需要声明一个对通知进行响应的方法即可。两者代码如下:
public abstract class Subject {
// 观察者列表
protected List<Observer> observers = new ArrayList<>();
// 增加观察者
public void add(Observer o) {
observers.add(o);
}
// 删除观察者
public void delete(Observer o) {
observers.remove(o);
}
// 通知所有观察者
public abstract void notifyObservers(String msg);
}
public interface Observer {
void response(String userName);
}
然后定义具体的被观察者角色以及观察者角色,即注册用户动作发出者以及对注册动作的响应类,其代码如下:
public class UserRegisterSubject extends Subject {
@Override
public void notifyObservers(String msg) {
for (Observer observer : observers) {
// 只通知用户注册类观察者
if (observer instanceof UserRegisterObserver) {
observer.response(msg);
}
}
}
}
public class UserRegisterObserver implements Observer {
@Override
public void response(String userName) {
System.out.println("[用户注册观察者]用户: [" + userName + "]已注册,发放新人福利:88元红包");
}
}
然后编写测试代码,其代码如下:
public class Test {
public static void main(String[] args) {
// 注册观察者
Observer observer = new UserRegisterObserver();
Subject subject = new UserRegisterSubject();
subject.add(observer);
// 模拟用户注册
System.out.println("用户A注册成功");
subject.notifyObservers("A");
}
}
输出结果为:
用户A注册成功
[用户注册观察者]用户: [A]已注册,发放新人福利:88元红包
由此可见,观察者 UserRegisterObserver 已经获取到被观察者 UserRegisterSubject 发送的通知消息并且做出响应。
本示例所演示的观察者模式是最基础的同步阻塞形式,一般我们在实际项目中使用观察者模式最多的情况就是基于 MQ 进行异步处理,例如上面的例子就是在用户注册后,作为生产者抛出一个注册消息,然后消费者订阅该 Topic 类型的消息,对注册消息进行消费,执行异步动作,如送出新人福利、初始化用户等级、发送注册成功短信等等。
4. 使用场景
观察者模式的使用场景是:
- 关联行为场景。需要注意的是,关联行为是可拆分的,而不是“组合”关系。
- 事件多级触发场景。
- 跨系统的消息交换场景,如消息队列的处理机制。
5. 小结
本文讲述了观察者模式,它的定义是——定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新。
观察者模式的优点是:
- 符合依赖倒置原则,观察者和被观察者之间是抽象耦合的关系,它们都非常容易扩展
- 建立了一套从被观察者到观察者之间的触发机制,如果两者之间存在某种依赖关系,如用户A关注用户B之后,用户A才会收到用户B发布的动态
观察者模式的缺点是:
- 如果一个被观察者有很多观察者,通知所有的观察者会花费较多的时间
- 观察者与被观察者之间可能会存在循环依赖关系,严重时可能导致系统崩溃
6. 参考资料
- 《设计模式之禅》(第2版)
最后,本文收录于个人语雀知识库: 我所理解的后端技术,欢迎来访。
文档信息
- 本文作者:Planeswalker23
- 本文链接:https://planeswalker23.github.io/2022/02/05/%E6%88%91%E6%89%80%E7%90%86%E8%A7%A3%E7%9A%84%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E7%B3%BB%E5%88%97-%E7%AC%AC12%E7%AF%87-ToC%E5%B9%B3%E5%8F%B0%E5%9F%BA%E4%BA%8E%E8%A7%82%E5%AF%9F%E8%80%85%E6%A8%A1%E5%BC%8F%E8%B5%A0%E9%80%81%E6%96%B0%E4%BA%BA%E7%A6%8F%E5%88%A9/
- 版权声明:本作品系原创,作者保留所有权利,未经作者允许,禁止转载和演绎。