本期内容为自己总结归档,共分6章,本人遇到过的面试问题会重点标记。
(若有任何疑问,可在评论区告诉我,看到就回复)
一、观察者模式的核心概念
1.1 观察者模式的定义
观察者模式(Observer Pattern)定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当主题对象状态发生变化时,会通知所有观察者对象,使它们能够自动更新。
1.2 观察者模式的结构
观察者模式包含四个关键角色:
-
Subject(主题/被观察者):维护一个观察者列表,提供添加、删除观察者的方法,以及通知所有观察者的方法
-
Observer(观察者):定义一个更新接口,用于在主题状态改变时接收通知
-
ConcreteSubject(具体主题):具体被观察的对象,存储状态,状态改变时通知观察者
-
ConcreteObserver(具体观察者):实现观察者接口,存储对具体主题的引用,实现更新逻辑
1.3 观察者模式 vs 发布-订阅模式
很多人容易混淆观察者模式和发布-订阅模式,它们确实相似但有所不同:
| 特性 | 观察者模式 | 发布-订阅模式 |
|---|---|---|
| 耦合度 | 松耦合 | 更松耦合 |
| 通信方式 | 直接调用观察者方法 | 通过消息队列/事件总线 |
| 关系 | 主题知道观察者 | 发布者不知道订阅者 |
| 同步性 | 通常是同步的 | 可以是异步的 |
| 实现复杂度 | 相对简单 | 更复杂 |
观察者模式可以看作是发布-订阅模式的简化版本。
1.4 设计要点
在设计观察者模式的程序时要注意以下几点:
-
要明确谁是观察者谁是被观察者,只要明白谁是关注对象,问题也就明白了。一般观察者与被观察者之间是多对一的关系,一个被观察对象可以有多个监听对象(观察者)。如一个编辑框,有鼠标点击的监听者,也有键盘的监听者,还有内容改变的监听者。
-
Observable 在发送广播通知的时候,无须指定具体的 Observer,Observer 可以自己决定是否要订阅 Subject 的通知。
-
被观察者至少需要有三个方法:添加监听者、移除监听者、通知 Observer 的方法 ;观察者至少要有一个方法:更新方法,更新当前的内容,作出相应的处理。
-
添加监听者、移除监听者在不同的模型称谓中可能会有不同命名,如观察者模型中一般,addObserver,removeObserver;在源-监听器(Source/Listener)模型中一般是 attach/detach,应用在桌面编程的窗口中,还可能是 attachWindow/detachWindow,或 Register/UnRegister。不要被名称迷糊了,不管他们是什么名称,其实功能都是一样的,就是添加/删除观察者。
二、观察者模式的实现方式
2.1 基础实现demo:天气监测系统
天气监测系统:气象站收集天气数据,多个显示设备需要实时显示这些数据。
java
// 1. 观察者接口
public interface Observer {
void update(float temperature, float humidity, float pressure);
}
// 2. 主题接口
public interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}
// 3. 具体主题:气象数据
public class WeatherData implements Subject {
private List<Observer> observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
observers = new ArrayList<>();
}
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
int index = observers.indexOf(observer);
if (index >= 0) {
observers.remove(index);
}
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(temperature, humidity, pressure);
}
}
// 当气象数据更新时调用
public void measurementsChanged() {
notifyObservers();
}
// 设置气象数据(模拟从气象站获取数据)
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
}
// 4. 具体观察者:当前状况显示
public class CurrentConditionsDisplay implements Observer {
private float temperature;
private float humidity;
public CurrentConditionsDisplay(Subject weatherData) {
weatherData.registerObserver(this);
}
@Override
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
display();
}
public void display() {
System.out.printf("当前状况: %.1f°C 和 %.1f%% 湿度%n", temperature, humidity);
}
}
// 5. 具体观察者:气象统计显示
public class StatisticsDisplay implements Observer {
private List<Float> temperatures = new ArrayList<>();
public StatisticsDisplay(Subject weatherData) {
weatherData.registerObserver(this);
}
@Override
public void update(float temperature, float humidity, float pressure) {
temperatures.add(temperature);
display();
}
public void display() {
float avg = temperatures.stream()
.reduce(0f, Float::sum) / temperatures.size();
float max = temperatures.stream().max(Float::compare).orElse(0f);
float min = temperatures.stream().min(Float::compare).orElse(0f);
System.out.printf("气象统计: 平均/最大/最小温度 = %.1f/%.1f/%.1f°C%n", avg, max, min);
}
}
// 6. 测试代码
public class WeatherStation {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
// 模拟气象数据更新
System.out.println("=== 第一次测量 ===");
weatherData.setMeasurements(25.0f, 65.0f, 1013.1f);
System.out.println("\n=== 第二次测量 ===");
weatherData.setMeasurements(26.5f, 70.0f, 1012.5f);
System.out.println("\n=== 第三次测量 ===");
weatherData.setMeasurements(24.8f, 90.0f, 1014.0f);
}
}
2.2 推模型 vs 拉模型
观察者模式有两种通知方式:
-
推模型(Push Model):主题将详细数据推送给观察者
-
拉模型(Pull Model):主题只通知观察者状态已改变,观察者自己拉取需要的数据
上面的示例使用的是推模型。下面是拉模型的实现:
java
// 拉模型的观察者接口
public interface PullObserver {
void update();
}
// 拉模型的主题接口
public interface PullSubject {
void registerObserver(PullObserver observer);
void removeObserver(PullObserver observer);
void notifyObservers();
// 提供获取数据的方法
float getTemperature();
float getHumidity();
float getPressure();
}
// 具体实现
public class PullWeatherData implements PullSubject {
private List<PullObserver> observers = new ArrayList<>();
private float temperature;
private float humidity;
private float pressure;
// 省略其他方法...
@Override
public void notifyObservers() {
for (PullObserver observer : observers) {
observer.update(); // 只通知,不传数据
}
}
// 观察者通过getter方法获取数据
@Override
public float getTemperature() { return temperature; }
@Override
public float getHumidity() { return humidity; }
@Override
public float getPressure() { return pressure; }
}
// 拉模型的观察者实现
public class PullCurrentConditionsDisplay implements PullObserver {
private PullSubject weatherData;
public PullCurrentConditionsDisplay(PullSubject weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
@Override
public void update() {
// 主动拉取需要的数据
float temperature = weatherData.getTemperature();
float humidity = weatherData.getHumidity();
System.out.printf("当前状况: %.1f°C 和 %.1f%% 湿度%n", temperature, humidity);
}
}
三、观察者模式的高级应用
3.1 事件驱动架构
观察者模式是事件驱动架构的基础。让我们实现一个简单的事件系统demo:
java
// 事件对象
public class WeatherEvent {
private final float temperature;
private final float humidity;
private final float pressure;
private final long timestamp;
public WeatherEvent(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
this.timestamp = System.currentTimeMillis();
}
// getter方法...
}
// 事件监听器接口
public interface EventListener {
void onEvent(WeatherEvent event);
}
// 事件管理器(简化版事件总线)
public class EventManager {
private Map<String, List<EventListener>> listeners = new HashMap<>();
public void subscribe(String eventType, EventListener listener) {
listeners.computeIfAbsent(eventType, k -> new ArrayList<>()).add(listener);
}
public void unsubscribe(String eventType, EventListener listener) {
List<EventListener> eventListeners = listeners.get(eventType);
if (eventListeners != null) {
eventListeners.remove(listener);
}
}
public void publish(String eventType, WeatherEvent event) {
List<EventListener> eventListeners = listeners.get(eventType);
if (eventListeners != null) {
for (EventListener listener : eventListeners) {
listener.onEvent(event);
}
}
}
}
// 使用示例
public class EventDrivenWeatherSystem {
public static void main(String[] args) {
EventManager eventManager = new EventManager();
// 注册监听器
eventManager.subscribe("weather.update", event -> {
System.out.println("记录到日志: " + event);
});
eventManager.subscribe("weather.update", event -> {
System.out.println("发送到仪表板: " + event.getTemperature() + "°C");
});
// 发布事件
WeatherEvent event = new WeatherEvent(25.5f, 70.0f, 1013.0f);
eventManager.publish("weather.update", event);
}
}
3.2 异步观察者模式
在实际应用中,观察者的处理可能是耗时的,这时需要异步通知:
java
public class AsyncWeatherData extends WeatherData {
private ExecutorService executor = Executors.newFixedThreadPool(3);
@Override
public void notifyObservers() {
for (Observer observer : observers) {
executor.submit(() -> {
try {
observer.update(temperature, humidity, pressure);
} catch (Exception e) {
// 处理异常,避免影响其他观察者
System.err.println("观察者处理失败: " + e.getMessage());
}
});
}
}
}
四、观察者模式在Spring框架中的应用
4.1 Spring的事件机制
Spring框架内置了强大的事件机制
java
// 1. 自定义事件
public class WeatherEvent extends ApplicationEvent {
private final float temperature;
private final float humidity;
public WeatherEvent(Object source, float temperature, float humidity) {
super(source);
this.temperature = temperature;
this.humidity = humidity;
}
public float getTemperature() { return temperature; }
public float getHumidity() { return humidity; }
}
// 2. 事件发布者
@Service
public class WeatherService {
private final ApplicationEventPublisher eventPublisher;
@Autowired
public WeatherService(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
public void updateWeather(float temperature, float humidity) {
// 业务逻辑...
// 发布事件
WeatherEvent event = new WeatherEvent(this, temperature, humidity);
eventPublisher.publishEvent(event);
}
}
// 3. 事件监听器(方式一:实现ApplicationListener接口)
@Component
public class WeatherEventListener implements ApplicationListener<WeatherEvent> {
@Override
public void onApplicationEvent(WeatherEvent event) {
System.out.println("收到天气事件: " + event.getTemperature() + "°C");
}
}
// 4. 事件监听器(方式二:使用@EventListener注解)
@Component
public class WeatherAnnotationListener {
@EventListener
public void handleWeatherEvent(WeatherEvent event) {
System.out.println("注解方式处理天气事件: " + event.getHumidity() + "%");
}
// 支持条件表达式
@EventListener(condition = "#event.temperature > 30")
public void handleHotWeather(WeatherEvent event) {
System.out.println("高温警告: " + event.getTemperature() + "°C");
}
}
4.2 Spring事件机制的特点
Spring的事件机制比基本观察者模式更强大:
-
类型安全:基于泛型的事件类型
-
支持同步和异步 :通过
@Async注解支持异步处理 -
事务绑定:支持事务事件监听
-
条件过滤:支持SpEL表达式的条件监听
-
事件继承:监听父类事件会收到子类事件
java
// 异步事件监听
@Component
public class AsyncWeatherListener {
@Async
@EventListener
public void handleAsync(WeatherEvent event) {
// 异步处理,不会阻塞主线程
System.out.println("异步处理天气事件");
}
}
// 事务相关事件
@Component
public class TransactionalWeatherListener {
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleAfterCommit(WeatherEvent event) {
// 事务提交后执行
System.out.println("事务提交后处理天气事件");
}
}
总结
5.1 何时使用观察者模式
观察者模式适用于以下场景:
当一个对象的状态改变需要通知其他对象,且不知道有多少对象需要通知时
当一个抽象模型有两个方面,其中一个方面依赖于另一个方面时
需要在运行时动态添加或删除监听器时
需要实现事件处理系统时
**5.2、**核心要点
设计原则:观察者模式体现了开闭原则(对扩展开放,对修改关闭)和依赖倒置原则(依赖于抽象而非具体实现)
实现方式:
基础实现:主题 + 观察者接口
推模型 vs 拉模型:根据需求选择合适的数据传递方式
异步通知:处理耗时的观察者逻辑
在Spring中的应用:
Spring事件机制是观察者模式的实现
支持同步/异步、条件过滤、事务绑定等高级特性