23种设计模式一观察者模式
·
一、引言
在软件开发的广阔天地中,设计模式 是解决特定问题的成熟方案。其中,观察者模式(Observer Pattern)作为行为型设计模式的重要成员,在构建松耦合、易扩展的系统架构中扮演着核心角色。
什么是观察者模式?
观察者模式 定义了对象间的一对多依赖关系,使得当一个对象(被观察者)的状态发生改变时,所有依赖于它的对象(观察者)都会得到通知并自动更新。这种模式又被称为 发布-订阅模式(Publish-Subscribe Pattern)。
核心作用
观察者模式的核心价值在于:
- 实现松耦合:被观察者无需知道具体观察者的存在
- 动态绑定:观察者可以随时注册或注销
- 广播通信:一个事件可以通知多个监听器
学习价值
掌握观察者模式将帮助你:
- 理解Spring事件机制、Vue响应式原理等框架核心思想
- 构建可维护的大型应用系统
- 提升代码的可测试性和扩展性
二、核心概念
模式结构
观察者模式包含以下核心角色:
1. Subject(被观察者/主题)
- 职责:维护观察者列表,提供注册、删除和通知方法
- 特点:状态变化时主动通知所有观察者
2. Observer(观察者)
- 职责:定义更新接口,接收被观察者的通知
- 特点:多个观察者可注册到同一被观察者
3. ConcreteSubject(具体被观察者)
- 职责:实现Subject接口,存储具体状态
4. ConcreteObserver(具体观察者)
- 职责:实现Observer接口,定义具体更新行为
工作原理流程图
┌─────────────────────────────────────────────────────────┐
│ 观察者模式工作流程 │
└─────────────────────────────────────────────────────────┘
观察者A注册 ───┐
├──→ 被观察者 ──→ 状态变化 ──→ 通知所有观察者
观察者B注册 ───┘ │
↓
观察者A收到通知 → 更新状态
观察者B收到通知 → 更新状态
三、实现方式
方式一:基于Java内置Observable和Observer
Java提供了内置的观察者模式支持,位于java.util包中。
完整代码示例
1. 创建被观察者类
import java.util.Observable;
/**
* 天气站 - 被观察者
* 继承Observable类,实现状态管理
*/
public class WeatherStation extends Observable {
private float temperature;
private float humidity;
private float pressure;
/**
* 测量数据更新
* 模拟数据变化场景
*/
public void measurementsChanged() {
setChanged(); // 标记状态已改变
notifyObservers(); // 通知所有观察者
}
/**
* 设置测量数据
* @param temperature 温度
* @param humidity 湿度
* @param pressure 气压
*/
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
// Getter方法
public float getTemperature() { return temperature; }
public float getHumidity() { return humidity; }
public float getPressure() { return pressure; }
}
2. 创建观察者类
import java.util.Observable;
import java.util.Observer;
/**
* 当前天气显示 - 观察者
* 实现Observer接口,定义更新逻辑
*/
public class CurrentConditionsDisplay implements Observer {
private Observable observable;
private float temperature;
private float humidity;
/**
* 构造函数,注册观察者
* @param observable 被观察者对象
*/
public CurrentConditionsDisplay(Observable observable) {
this.observable = observable;
observable.addObserver(this); // 注册为观察者
}
/**
* 更新方法
* 被观察者状态变化时自动调用
*/
@Override
public void update(Observable obs, Object arg) {
if (obs instanceof WeatherStation) {
WeatherStation weatherStation = (WeatherStation) obs;
this.temperature = weatherStation.getTemperature();
this.humidity = weatherStation.getHumidity();
display();
}
}
/**
* 显示当前天气信息
*/
public void display() {
System.out.println("当前天气状况:");
System.out.println("温度: " + temperature + "°C");
System.out.println("湿度: " + humidity + "%");
System.out.println("------------------------");
}
}
3. 使用示例
/**
* 观察者模式测试类
*/
public class ObserverPatternDemo {
public static void main(String[] args) {
// 创建被观察者
WeatherStation weatherStation = new WeatherStation();
// 创建观察者并注册
CurrentConditionsDisplay currentDisplay =
new CurrentConditionsDisplay(weatherStation);
// 模拟数据变化
System.out.println("第一次数据更新:");
weatherStation.setMeasurements(26.5f, 65f, 1013.1f);
System.out.println("第二次数据更新:");
weatherStation.setMeasurements(28.3f, 70f, 1012.5f);
System.out.println("第三次数据更新:");
weatherStation.setMeasurements(24.8f, 58f, 1014.2f);
}
}
输出结果:
第一次数据更新:
当前天气状况:
温度: 26.5°C
湿度: 65.0%
------------------------
第二次数据更新:
当前天气状况:
温度: 28.3°C
湿度: 70.0%
------------------------
第三次数据更新:
当前天气状况:
温度: 24.8°C
湿度: 58.0%
------------------------
注意:Java内置的Observable类在Java 9中已被标记为
@Deprecated,官方推荐使用自定义实现方式。
方式二:基于接口编程的自定义实现
这是更加灵活和推荐的实现方式。
1. 定义观察者接口
/**
* 观察者接口
* 定义所有观察者必须实现的更新方法
*/
public interface Observer {
/**
* 更新方法
* @param temperature 温度
* @param humidity 湿度
* @param pressure 气压
*/
void update(float temperature, float humidity, float pressure);
}
2. 定义被观察者接口
/**
* 被观察者接口
* 定义注册、删除和通知观察者的方法
*/
public interface Subject {
/**
* 注册观察者
* @param observer 观察者对象
*/
void registerObserver(Observer observer);
/**
* 移除观察者
* @param observer 观察者对象
*/
void removeObserver(Observer observer);
/**
* 通知所有观察者
*/
void notifyObservers();
}
3. 实现具体被观察者
import java.util.ArrayList;
import java.util.List;
/**
* 自定义天气站实现
* 实现Subject接口,管理观察者列表
*/
public class CustomWeatherStation implements Subject {
private List<Observer> observers;
private float temperature;
private float humidity;
private float pressure;
public CustomWeatherStation() {
observers = new ArrayList<>();
}
@Override
public void registerObserver(Observer observer) {
if (observer != null && !observers.contains(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 CustomWeatherDisplay implements Observer {
private float temperature;
private float humidity;
private float pressure;
private Subject weatherStation;
public CustomWeatherDisplay(Subject weatherStation) {
this.weatherStation = weatherStation;
weatherStation.registerObserver(this);
}
@Override
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
display();
}
public void display() {
System.out.println("=== 实时天气面板 ===");
System.out.printf("温度: %.1f°C\n", temperature);
System.out.printf("湿度: %.1f%%\n", humidity);
System.out.printf("气压: %.1f hPa\n", pressure);
System.out.println("===================");
}
}
5. 测试代码
/**
* 自定义观察者模式演示
*/
public class CustomObserverDemo {
public static void main(String[] args) {
// 创建被观察者
CustomWeatherStation weatherStation = new CustomWeatherStation();
// 创建多个观察者
CustomWeatherDisplay display1 = new CustomWeatherDisplay(weatherStation);
CustomWeatherDisplay display2 = new CustomWeatherDisplay(weatherStation);
// 模拟数据更新
System.out.println("=== 天气数据更新测试 ===\n");
weatherStation.setMeasurements(25.5f, 60f, 1013.0f);
// 移除一个观察者
weatherStation.removeObserver(display2);
System.out.println("\n=== 移除display2后的更新 ===\n");
weatherStation.setMeasurements(27.0f, 62f, 1012.5f);
}
}
四、应用场景分析
1. Spring框架中的应用
Spring框架大量使用了观察者模式:
Spring事件机制
/**
* 自定义事件
*/
public class CustomEvent extends ApplicationEvent {
private String message;
public CustomEvent(Object source, String message) {
super(source);
this.message = message;
}
public String getMessage() {
return message;
}
}
/**
* 事件监听器
*/
@Component
public class CustomEventListener {
@EventListener
public void handleCustomEvent(CustomEvent event) {
System.out.println("收到事件通知: " + event.getMessage());
}
}
/**
* 发布事件
*/
@Service
public class EventPublisherService {
@Autowired
private ApplicationContext applicationContext;
public void publishEvent(String message) {
CustomEvent event = new CustomEvent(this, message);
applicationContext.publishEvent(event);
}
}
应用场景:
- 用户注册成功后发送欢迎邮件
- 订单状态变更通知
- 缓存更新事件
2. Android开发中的应用
Android的事件监听机制:
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 处理点击事件
}
});
3. 实际项目案例
股票价格监控系统:
/**
* 股票被观察者
*/
public class StockSubject implements Subject {
private List<Observer> investors = new ArrayList<>();
private double stockPrice;
public void setStockPrice(double price) {
this.stockPrice = price;
notifyObservers();
}
@Override
public void notifyObservers() {
for (Observer investor : investors) {
investor.update(stockPrice);
}
}
// 其他方法实现...
}
/**
* 投资者观察者
*/
public class Investor implements Observer {
private String name;
private double buyPrice;
public Investor(String name, double buyPrice) {
this.name = name;
this.buyPrice = buyPrice;
}
@Override
public void update(double currentPrice) {
double profit = currentPrice - buyPrice;
System.out.printf("投资者 %s: 当前股价 %.2f, 盈亏: %.2f\n",
name, currentPrice, profit);
}
}
五、优缺点总结
优势分析
| 优势 | 说明 | 实际价值 |
|---|---|---|
| 松耦合 | 被观察者和观察者之间抽象耦合 | 提高代码可维护性 |
| 广播通信 | 一对多的消息传递 | 简化事件处理逻辑 |
| 动态扩展 | 运行时增删观察者 | 系统灵活性高 |
| 符合开闭原则 | 新增观察者无需修改被观察者 | 易于功能扩展 |
潜在缺点
-
内存泄漏风险
- 观察者忘记注销会导致内存泄漏
- 解决方案:在对象销毁时主动调用
removeObserver()
-
执行顺序不确定性
- 多个观察者的通知顺序不确定
- 影响:某些业务场景需要特定执行顺序
-
性能考虑
- 观察者过多可能影响通知效率
- 建议:异步处理或使用线程池
-
调试困难
- 事件驱动模式下调用链不直观
- 解决方案:添加详细日志记录
代码示例:避免内存泄漏
/**
* 观察者使用示例 - 正确的资源释放
*/
public class ObserverUsageExample implements AutoCloseable {
private Subject weatherStation;
private Observer observer;
public ObserverUsageExample() {
weatherStation = new CustomWeatherStation();
observer = new CustomWeatherDisplay(weatherStation);
}
@Override
public void close() {
// 重要:使用完毕后注销观察者
if (weatherStation != null && observer != null) {
weatherStation.removeObserver(observer);
System.out.println("观察者已注销,避免内存泄漏");
}
}
}
六、与其他模式对比
观察者模式 vs 发布-订阅模式
虽然经常被混淆,但两者有明显区别:
| 对比维度 | 观察者模式 | 发布-订阅模式 |
|---|---|---|
| 耦合度 | 观察者与被观察者直接依赖 | 通过消息中间件间接通信 |
| 通信方式 | 同步调用 | 异步消息传递 |
| 扩展性 | 相对有限 | 更好的扩展性 |
| 典型应用 | Java内置Observer、Spring事件 | Kafka、RabbitMQ消息队列 |
发布-订阅模式示例
/**
* 简化的发布-订阅模式(使用消息队列概念)
*/
public class MessageBroker {
private Map<String, List<Subscriber>> topicMap = new HashMap<>();
public void subscribe(String topic, Subscriber subscriber) {
topicMap.computeIfAbsent(topic, k -> new ArrayList<>())
.add(subscriber);
}
public void publish(String topic, String message) {
List<Subscriber> subscribers = topicMap.get(topic);
if (subscribers != null) {
for (Subscriber subscriber : subscribers) {
subscriber.onMessage(message);
}
}
}
}
interface Subscriber {
void onMessage(String message);
}
与其他设计模式的关系
- 中介者模式:观察者模式是一对多,中介者模式是多对多协调
- 责任链模式:观察者模式是广播,责任链是链式传递
- MVC模式:Model和View之间常使用观察者模式
七、最佳实践
1. 设计建议
✅ 推荐做法
/**
* 良好的观察者模式设计
*/
public interface EventObserver {
/**
* 使用泛型提高类型安全性
*/
<T> void onEvent(T event);
}
/**
* 被观察者使用线程池异步通知
*/
public class AsyncSubject implements Subject {
private ExecutorService executor = Executors.newFixedThreadPool(10);
private List<Observer> observers = new CopyOnWriteArrayList<>();
@Override
public void notifyObservers() {
// 异步通知,避免阻塞主线程
for (Observer observer : observers) {
executor.submit(() -> observer.update(null));
}
}
public void shutdown() {
executor.shutdown();
}
}
❌ 避免的做法
// 不要在update方法中执行耗时操作
@Override
public void update(float temperature, float humidity, float pressure) {
// 错误:阻塞主线程的耗时操作
try {
Thread.sleep(5000); // 不要这样做!
} catch (InterruptedException e) {
e.printStackTrace();
}
}
2. 使用注意事项
-
线程安全性
- 使用
CopyOnWriteArrayList或同步机制保护观察者列表 - 避免在通知过程中修改观察者列表
- 使用
-
异常处理
- 单个观察者的异常不应影响其他观察者
- 建议在update方法中添加try-catch
@Override
public void notifyObservers() {
for (Observer observer : observers) {
try {
observer.update(data);
} catch (Exception e) {
// 记录异常,但继续通知其他观察者
System.err.println("观察者更新失败: " + e.getMessage());
}
}
}
- 性能优化
- 对于高频事件,考虑使用事件聚合
- 使用异步处理提升性能
3. 实际项目应用模板
/**
* 企业级观察者模式模板
*/
public class EventBusTemplate {
// 事件注册表
private Map<Class<?>, List<EventHandler>> handlers = new ConcurrentHashMap<>();
/**
* 注册事件处理器
*/
public <T> void register(Class<T> eventType, EventHandler<T> handler) {
handlers.computeIfAbsent(eventType, k -> new CopyOnWriteArrayList<>())
.add(handler);
}
/**
* 发布事件
*/
public <T> void publish(T event) {
List<EventHandler> eventHandlers = handlers.get(event.getClass());
if (eventHandlers != null) {
eventHandlers.forEach(handler -> handler.handle(event));
}
}
/**
* 事件处理器接口
*/
@FunctionalInterface
public interface EventHandler<T> {
void handle(T event);
}
}
八、总结
观察者模式作为经典的设计模式,在Java开发生态中占据重要地位。通过本文的学习,我们了解到:
核心要点回顾:
- ✅ 掌握观察者模式的定义和核心角色
- ✅ 了解两种实现方式及其适用场景
- ✅ 理解在Spring等框架中的实际应用
- ✅ 认识优缺点并掌握最佳实践
进阶学习建议:
- 深入学习Spring事件机制源码
- 研究Reactive Programming响应式编程
- 了解消息队列的实现原理
适用场景判断:
当需要实现对象间的一对多依赖、解耦核心逻辑与副作用处理、实现事件驱动架构时,观察者模式都是理想的选择。
更多推荐



所有评论(0)