设计模式第六章(观察者模式)
观察者模式是一种行为设计模式,它定义了对象之间的一对对多依赖关系:当一个对象(被观察者)的状态发生变化时,所有依赖它的对象(观察者)会自动收到通知并更新。
这种模式的核心思想是解耦被观察者和观察者,让它们可以独立变化,同时保持联动。
前言
关键角色
- 被观察者(Observable/Subject)
- 维护一个观察者列表,提供添加、移除观察者的方法
- 当自身状态变化时,主动通知所有注册的观察者
- 观察者(Observer)
- 定义一个更新接口,当收到被观察者的通知时,执行相应的处理逻辑
- 可以有多个不同的观察者实现,各自处理方式不同
工作流程
- 观察者通过被观察者提供的方法(如
addObserver()
)注册自己 - 被观察者状态发生改变时,调用自身的通知方法(如
notifyObservers()
) - 被观察者遍历所有注册的观察者,调用它们的更新方法(如
update()
) - 观察者收到通知后,根据被观察者的状态变化执行具体操作
典型场景
- 消息订阅:公众号(被观察者)更新文章后,所有订阅者(观察者)收到推送
- 数据监控:传感器(被观察者)检测到温度变化,仪表盘、报警器(观察者)分别响应
- GUI 事件处理:按钮(被观察者)被点击时,关联的回调函数(观察者)执行
优点
- 降低耦合:被观察者无需知道观察者的具体实现,只需调用统一的更新接口
- 扩展性好:新增观察者时,无需修改被观察者代码,符合 "开闭原则"
- 联动灵活:可以动态添加 / 移除观察者,实时调整响应关系
例如,在天气系统中:
- 气象站(被观察者)收集到温度变化
- 手机 APP、显示屏、报警器(多个观察者)会同时收到通知并更新显示或触发警报
- 若后续需要添加新的观察者(如恒温控制器),只需让它实现观察者接口并注册即可,无需修改气象站代码。
实战第一版本
故事背景:一个天气站如果更新了天气,需要将天气通知订阅了天气更新的某些人和事。我们来用一个初级版本直接堆功能来实现。
用户信息
java
public class User {
private final String name;
private final Consumer<String> consumer;
public User(String name, Consumer<String> consumer) {
this.name = name;
this.consumer = consumer;
}
public void notify(String inf) {
consumer.accept(inf);
}
}
天气站
java
public class WeatherStation {
private final List<User> users = new ArrayList<User>();
public void addUser(User user) {
users.add(user);
}
public String getInfo() {
if (new Random().nextBoolean()) {
return "晴天";
}
return "雨天";
}
public void start() {
while (true) {
String info = getInfo();
for (User user : users) {
user.notify(info);
}
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
测试用例
java
public class Main {
public static void main(String[] args) {
//TIP Press <shortcut actionId="ShowIntentionActions"/> with your caret at the highlighted text
// to see how IntelliJ IDEA suggests fixing it.
System.out.println("Hello and welcome!");
User tom = new User("tom", inf -> {
if (inf.equals("晴天")) {
System.out.println("晴天了,tom快出来玩啊~~~");
} else {
System.out.println("下雨天,tom 你需要在家待着了~~~");
}
});
User jerry = new User("jerry", inf -> {
if (inf.equals("晴天")) {
System.out.println("晴天了,jerry快出来游泳了~~");
}
});
//订阅天气信息
WeatherStation station = new WeatherStation();
station.addUser(tom);
station.addUser(jerry);
station.start();
// 问题点,我本来只是一个天气站,怎么通知的事情还需要我来做呢,
// 通知的事情是不是需要电视台来做呢?如果按照单一职责,那么通知的事情就不应该是我来做,如果找我订阅的是一个组织,我是不是又需要在里面加上一个组织的消费呢
// 接下来,我们就需要一个tvstation来进行通知
}
}

问题点
-
问题点,我本来只是一个天气站,怎么通知的事情还需要我来做呢
-
通知的事情是不是需要电视台来做呢?如果按照单一职责,那么通知的事情就不应该是我来做,如果找我订阅的是一个组织,我是不是又需要在里面加上一个组织的消费呢
-
接下来,我们就需要一个tvstation来进行通知
实战第二版本
我们增加了一个电视台的角色,所有的订阅都去电视台,天气信息的更新我只负责通知到电视台,由电视台再通知被订阅的人
用户信息
java
public class User {
private final String name;
private final Consumer<String> consumer;
public User(String name, Consumer<String> consumer) {
this.name = name;
this.consumer = consumer;
}
public void notify(String inf) {
consumer.accept(inf);
}
}
电视台
- 提供一个订阅的入口
- 提供一个发布的入口
java
public class TvStation {
private final List<User> userList = new ArrayList<>();
// 订阅的入口
public void addUser(User user) {
userList.add(user);
}
public void publish(String inf) {
for (User user : userList) {
user.notify(inf);
}
}
}
天气站
java
public class WeatherStation {
//电视台
private final TvStation tvStation;
public WeatherStation(TvStation tvStation) {
this.tvStation = tvStation;
}
public String getInfo() {
if (new Random().nextBoolean()) {
return "晴天";
}
return "雨天";
}
public void start() {
while (true) {
String info = getInfo();
//我只需要把我得消息给到电视台,由电视台进行这个通知的动作
tvStation.publish(info);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
测试用例
java
public class Main {
public static void main(String[] args) {
//TIP Press <shortcut actionId="ShowIntentionActions"/> with your caret at the highlighted text
// to see how IntelliJ IDEA suggests fixing it.
System.out.println("Hello and welcome!");
User tom = new User("tom", inf -> {
if (inf.equals("晴天")) {
System.out.println("晴天了,tom快出来玩啊~~~");
} else {
System.out.println("下雨天,tom 你需要在家待着了~~~");
}
});
User jerry = new User("jerry", inf -> {
if (inf.equals("晴天")) {
System.out.println("晴天了,jerry快出来游泳了~~");
}
});
TvStation tvStation = new TvStation();
//两个用户订阅了电视台的消息
tvStation.addUser(tom);
tvStation.addUser(jerry);
// 获取天气信息
WeatherStation weatherStation = new WeatherStation(tvStation);
weatherStation.start();
// 天气只负责获取信息,通知由电视台进行
// 优化点,如果用户需要订阅 增加了一个新闻类的信息该怎么办呢
// 下一节,我们将抽象发布布订阅模式
}
}

问题点
- 如果用户需要订阅 一个新闻类的信息该怎么办呢,我们是不是需要在里面再继续加一个新闻的类的,下一章节使用订阅发布模式来解耦
实战第三版本
- 事件,所有的需要通知的事件都需要实现该接口。
- 事件监听,所有希望得到通知的都需要实现该接口
事件总线接口
java
public interface Event {
/**
* 时间戳
* @return
*/
long timestamp();
/**
* 数据个格式
* @return
*/
Object source();
}
事件总线抽象基类
java
public abstract class BaseEvent implements Event {
@Override
public long timestamp() {
return System.currentTimeMillis();
}
}
天气更新的事件
如果天气更新了,那么我事先事件这个接口,信息都包装为一个 event 事件信息
public class WeatherUpdEvent extends BaseEvent{
//天气信息
private final String info;
public WeatherUpdEvent(String info) {
this.info = info;
}
@Override
public Object source() {
return info;
}
}
事件监听
java
public interface ListenerEvent {
/**
* 监听事件
* @param event
*/
void onEvent(Event event);
}
订阅感兴趣的事件
实际为最终消费端。拿到事件,然后做什么
java
public class User implements ListenerEvent {
private final String name;
private final Consumer<String> consumer;
public User(String name, Consumer<String> consumer) {
this.name = name;
this.consumer = consumer;
}
private void notify(String inf) {
consumer.accept(inf);
}
@Override
public void onEvent(Event event) {
if (event instanceof WeatherUpdEvent) {
notify(event.source().toString());
}
}
}
事件总线
我们监听订阅了event时间下所有的订阅者。
java
public class TvStation {
private final Map<Class<? extends Event>,List<ListenerEvent>> listenerEventMap = new HashMap<>();
// 订阅的入口
public void subscribe(ListenerEvent event,Class<? extends Event> eventType) {
listenerEventMap.computeIfAbsent(eventType,k -> new ArrayList<>()).add(event);
}
public void publish(Event event) {
Class<? extends Event> evnetClass = event.getClass();
List<ListenerEvent> listenerEvents = listenerEventMap.get(evnetClass);
if (listenerEvents != null) {
for (ListenerEvent listener : listenerEvents) {
listener.onEvent(event);
}
}
}
}
天气站
我们需要将我们的天气信息包装为一个event 的事件,这个时候就是天气更新的事件,我们进行包装一次。
java
public class WeatherStation {
//电视台 我们成为消息总线
private final TvStation tvStation;
public WeatherStation(TvStation tvStation) {
this.tvStation = tvStation;
}
public String getInfo() {
if (new Random().nextBoolean()) {
return "晴天";
}
return "雨天";
}
public void start() {
while (true) {
String info = getInfo();
WeatherUpdEvent weatherUpdEvent = new WeatherUpdEvent(info);
tvStation.publish(weatherUpdEvent);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
测试用例
java
public class Main {
public static void main(String[] args) {
User tom = new User("tom", inf -> {
if (inf.equals("晴天")) {
System.out.println("晴天了,tom快出来玩啊~~~");
} else {
System.out.println("下雨天,tom 你需要在家待着了~~~");
}
});
User jerry = new User("jerry", inf -> {
if (inf.equals("晴天")) {
System.out.println("晴天了,jerry快出来游泳了~~");
}
});
TvStation tvStation = new TvStation();
tvStation.subscribe(tom, WeatherUpdEvent.class);
tvStation.subscribe(jerry, WeatherUpdEvent.class);
WeatherStation station = new WeatherStation(tvStation);
station.start();
}
}

spring使用观察者模式
@Autowired
private ApplicationContext context;
// 事件发布器
@Autowired
private ApplicationEventPublisher publisher;
// 事件广播
@Autowired
private ApplicationEventMulticaster multicaster;
ApplicationContext
我们看到 ApplicationContext 继承ApplicationEventPublisher,那么这两个是不是就是一个对象呢。

实战部分
我们定义了一个controller,当用户注册的时候,我们需要像用户发送邮件,送新手礼物。
java
@GetMapping("regiest")
public String regiest(@RequestParam("userName") String userName) {
publisher.publishEvent(new RegisterEvent(userName));
//context.publishEvent(new RegisterEvent(userName));
//multicaster.multicastEvent(new RegisterEvent(userName));
return "success";
}
定义用户注册的事件
java
public class RegisterEvent extends ApplicationEvent {
public RegisterEvent(Object user) {
super(user);
}
public String getUserName() {
return getSource().toString();
}
}
监听用户注册成功事件
-
发放礼包
java@Service public class GiftService { @EventListener public void registerEvent(RegisterEvent event) { String userName = event.getUserName(); System.out.println("发放新手礼品给: " + userName); } }
-
邮件通知
java@Service public class EmailService { @EventListener public void registerEvent(RegisterEvent event) { String userName = event.getUserName(); System.out.println("给用户: " + userName + "发送邮件"); } }
启动类测试

源码分析部分
- org.springframework.context.support.AbstractApplicationContext#publishEvent(java.lang.Object, org.springframework.core.ResolvableType)
- org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType)
- org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType)
- org.springframework.context.event.ApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType)
- org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType)
- org.springframework.context.event.SimpleApplicationEventMulticaster#multicastEvent(org.springframework.context.ApplicationEvent, org.springframework.core.ResolvableType)

