🔔观察者模式:代码界的“雷达”,让对象联动更智能!
一、观察者模式初相识

✨在生活中,我们经常需要“联动响应”:外卖订单状态更新时,商家和用户同时收到通知;股票价格波动时,关注者实时收到提醒。在代码世界里,观察者模式就像这样的“雷达系统”,让多个对象间的状态变化自动同步,实现松耦合联动!
观察者模式(Observer Pattern)定义了对象间一种一对多的依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。用专业术语来讲,这个模式中包含两种角色:
主题(Subject):也叫被观察者,拥有状态并维护观察者列表,状态变化时负责通知观察者。
观察者(Observer):接收主题通知,根据通知更新自身状态。
二、核心概念全解析
2.1 模式定义与原理
观察者模式定义对象间一对多的依赖关系,当一个对象(被观察者)状态发生改变时,所有依赖它的对象(观察者)都会得到通知并自动更新。它基于“发布-订阅”机制,被观察者(发布者)维护一个观察者(订阅者)列表,状态变化时主动“广播”通知,实现两者解耦,让它们可独立变化、复用。
举个例子,气象台就像被观察者,当它监测到暴雨天气时,会发布暴雨预警。市民们则是观察者,他们通过短信、天气 APP 等不同渠道接收预警通知,这就是典型的观察者模式应用场景。
2.2 四大核心角色
在观察者模式中,有四个核心角色,它们紧密协作,共同完成了对象间的状态通知和更新,具体如下:
抽象主题(Subject):定义添加 / 删除观察者的接口,为具体主题提供统一规范,不涉及具体业务逻辑。就好比气象台的预警系统,它规定了添加和删除接收预警对象(观察者)的方式,是整个预警机制的抽象规范。
具体主题(ConcreteSubject):实现主题逻辑,维护观察者列表,状态变化时通知观察者。例如中央气象台,它负责监测天气状况,在确定有暴雨等天气变化时,会根据维护的观察者列表,向市民等观察者发送预警通知。
抽象观察者(Observer):定义更新接口,让具体观察者实现,保证所有观察者有统一更新方法。市民接收预警的行为可以看作抽象观察者,它定义了接收预警后进行相应处理的接口规范。
具体观察者(ConcreteObserver):实现更新逻辑,响应状态变化。比如市民通过短信接收预警,这就是具体观察者的行为。当收到短信预警后,市民会根据预警内容采取相应措施,如减少外出、准备雨具等。
三、Java 代码实战
3.1 基础案例:天气预警系统
我们以一个天气预警系统为例,看看观察者模式在 Java 中如何实现。气象站作为被观察者,当天气状况(如温度、湿度、气压)发生变化时,需要通知多个关注天气的观察者,比如手机客户端、电视客户端等。
首先,定义抽象主题接口Subject,包含注册、移除观察者和通知观察者的方法:
import java.util.ArrayList;
import java.util.List;
// 抽象主题接口
interface Subject {
// 注册观察者
void registerObserver(Observer observer);
// 移除观察者
void removeObserver(Observer observer);
// 通知观察者
void notifyObservers();
}
接着,定义抽象观察者接口Observer,包含接收通知后的更新方法:
// 抽象观察者接口
interface Observer {
// 更新方法,接收天气数据
void update(float temperature, float humidity, float pressure);
}
然后,实现具体主题WeatherData,继承Subject接口,并维护一个观察者列表:
// 具体主题,气象站
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();
}
}
再实现具体观察者,以手机客户端为例:
// 具体观察者,手机客户端
class MobileClient implements Observer {
private float temperature;
private float humidity;
private float pressure;
@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.println("温度:" + temperature + "°C");
System.out.println("湿度:" + humidity + "%");
System.out.println("气压:" + pressure + "hPa");
}
}
最后,在测试类中进行测试:
public class ObserverPatternDemo {
public static void main(String[] args) {
// 创建气象站
WeatherData weatherData = new WeatherData();
// 创建手机客户端观察者
MobileClient mobileClient = new MobileClient();
// 注册手机客户端到气象站
weatherData.registerObserver(mobileClient);
// 模拟气象数据更新
weatherData.setMeasurements(25.0f, 60.0f, 1013.0f);
}
}
3.2 执行结果
当WeatherData中的气象数据更新时,会自动通知已注册的MobileClient,控制台输出:
手机客户端收到天气更新:
温度:25.0°C
湿度:60.0%
气压:1013.0hPa
从结果可以清晰看到,气象站(被观察者)状态变化时,手机客户端(观察者)及时收到通知并更新显示,完美体现了观察者模式的联动效果。
四、应用场景大揭秘
观察者模式在软件开发中应用广泛,能巧妙解决各种对象间的依赖与通知问题,以下是一些常见应用场景:
事件监听:在图形用户界面(GUI)开发中,按钮点击事件、键盘输入事件等都可以用观察者模式实现。例如,当用户点击按钮时,按钮作为被观察者,注册的监听器作为观察者,按钮状态改变(被点击)时通知监听器执行相应操作,像提交表单、打开新窗口等。比如在一个登录界面中,登录按钮被点击后,会通知注册的登录验证监听器,进行用户名和密码的验证操作。
消息队列:Kafka 等消息队列系统采用发布-订阅模式,本质上就是观察者模式的应用。生产者发布消息到主题(相当于被观察者),多个消费者订阅主题(相当于观察者),消息发布时消费者能及时接收并处理。以电商订单消息处理为例,订单创建消息发布到 Kafka 主题后,库存系统、物流系统等作为消费者订阅该主题,获取订单消息后分别进行库存扣减、安排物流等操作。
系统监控:在服务器监控系统中,需要实时监控 CPU 使用率、内存使用率等指标。监控指标作为被观察者,监控客户端作为观察者。当 CPU 使用率超过阈值(状态变化),监控系统会通知相关观察者,如发送邮件给管理员、在监控面板上显示告警信息等。例如,当服务器 CPU 使用率达到 80% 时,监控系统会向管理员的手机 APP 发送告警通知。
电商订单:在电商系统中,当订单状态发生变化,如支付成功后,需要同步更新库存、物流信息。订单作为被观察者,库存系统和物流系统作为观察者。订单支付成功(状态改变)时,通知库存系统扣减库存,通知物流系统安排发货。比如,用户在电商平台下单并支付成功后,库存系统会自动减少相应商品的库存数量,物流系统会开始安排商品的配送。
五、优缺点大 PK
在实际应用中,观察者模式就像一把双刃剑,有其独特的优势,也存在一定的局限性,我们来详细分析一下。
5.1 优点闪光点
解耦性强:观察者与被观察者之间通过抽象接口交互,彼此独立,降低耦合。就像微信公众号(被观察者)和关注者(观察者),公众号无需了解每个关注者的具体信息,只负责推送文章(通知),关注者自主接收文章并处理。这样一来,公众号的内容更新、功能升级等操作,都不会影响到关注者的其他业务逻辑,反之亦然,双方可以独立发展和变化。
扩展性佳:新增观察者时,只需实现抽象观察者接口,被观察者无需修改。比如电商系统中,新增加一种物流状态变更通知方式(如短信通知),只需要实现对应的观察者类,将其注册到订单状态(被观察者)上,即可完成扩展,而订单状态相关的代码无需任何改动。这使得系统能够轻松应对不断变化的业务需求,具有很强的灵活性。
实时性好:被观察者状态变化时,立即通知观察者,保证数据一致性。以股票交易系统为例,当股票价格(被观察者)发生变动时,关注该股票的投资者(观察者)能实时收到价格变动通知,及时做出交易决策。这种实时性确保了信息的及时传递,让各个相关方能够基于最新的数据进行操作,避免因信息滞后而导致的决策失误。
5.2 缺点放大镜
性能开销大:当观察者数量众多时,通知所有观察者的操作可能产生较大开销,影响系统性能。比如一个热门微博话题(被观察者)有几百万粉丝(观察者)关注,博主发布新内容(通知)时,要向所有粉丝推送消息,可能会消耗大量的网络带宽和服务器资源,导致系统响应变慢。在这种情况下,就需要考虑优化通知机制,如采用分批通知、异步通知等方式。
循环依赖风险:若不小心设计,可能出现观察者与被观察者相互引用,导致循环依赖。假设在一个社交系统中,用户 A(观察者)关注了用户 B(被观察者),同时用户 B 又关注了用户 A,并且在用户状态更新时,双方的更新逻辑中都存在相互调用对方通知方法的情况,就会形成循环依赖,导致系统陷入死循环,最终崩溃。为了避免这种情况,在设计时需要谨慎考虑对象之间的依赖关系,确保依赖的单向性。
通知顺序不确定:在同步通知中,若某一观察者处理逻辑耗时较长,会阻塞后续观察者接收通知。比如在一个任务调度系统中,任务(被观察者)完成后通知多个监听器(观察者)进行后续处理,若其中一个监听器的处理逻辑是进行复杂的数据库查询操作,耗时较长,那么其他监听器就需要等待该监听器处理完成后才能接收通知并执行相应操作,这可能会影响整个系统的效率。为了解决这个问题,可以考虑采用异步通知的方式,让每个观察者的处理逻辑在独立的线程中执行。
六、避坑指南
在使用观察者模式时,也有一些容易踩坑的地方,需要我们特别注意,掌握一些技巧可以让我们更高效地运用这个模式。
6.1 异步通知,性能飞升
当观察者众多时,同步通知可能导致性能瓶颈。可以使用多线程或线程池实现异步通知,让观察者在后台线程处理通知,互不干扰。比如在电商订单支付成功通知场景中,使用线程池将库存更新、物流安排等观察者任务异步执行,提升系统响应速度。具体实现可以参考如下代码:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
// 定义线程池
ExecutorService executorService = Executors.newFixedThreadPool(5);
// 在通知观察者方法中使用线程池异步执行
public void notifyObservers() {
for (Observer observer : observers) {
executorService.submit(() -> {
observer.update(temperature, humidity, pressure);
});
}
}
6.2 精确通知,有的放矢
若被观察者状态变化类型多样,可能不需要通知所有观察者。可以为通知添加类型或条件判断,让观察者只接收关心的事件通知。例如在一个游戏系统中,角色状态变化有生命值变化、等级提升等,不同的系统(如战斗系统、任务系统)作为观察者,只关心与自己相关的角色状态变化,通过添加事件类型判断,实现精确通知,减少不必要的处理。实现方式如下:
// 定义事件类型枚举
enum EventType {
HEALTH_CHANGE, LEVEL_UP
}
// 在通知方法中添加事件类型参数
public void notifyObservers(EventType eventType) {
for (Observer observer : observers) {
if (observer.isInterestedIn(eventType)) {
observer.update(eventType);
}
}
}
6.3 线程安全,稳如泰山
在多线程环境下,观察者列表的操作需保证线程安全。使用线程安全集合,如CopyOnWriteArrayList,它在修改时复制底层数组,读操作无锁,保证线程安全。以股票行情监控系统为例,多个线程可能同时注册或取消对某股票的观察,使用CopyOnWriteArrayList存储观察者,确保多线程操作的正确性。代码示例如下:
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
// 使用CopyOnWriteArrayList存储观察者
private List<Observer> observers = new CopyOnWriteArrayList<>();
6.4 结合责任链,威力加倍
在复杂业务场景中,单一观察者模式可能不够用。可以与责任链模式结合,观察者按顺序处理通知,前一个观察者处理完传递给下一个。例如在审批流程中,审批请求(被观察者状态变化)依次经过不同层级的审批者(观察者),每个审批者按职责处理,实现更灵活、强大的业务逻辑。结合示例代码如下:
// 责任链模式中的抽象处理者
abstract class Handler {
protected Handler nextHandler;
public void setNextHandler(Handler nextHandler) {
this.nextHandler = nextHandler;
}
public abstract void handleRequest(float temperature, float humidity, float pressure);
}
// 具体观察者同时也是责任链中的具体处理者
class ConcreteObserver extends Handler implements Observer {
@Override
public void update(float temperature, float humidity, float pressure) {
handleRequest(temperature, humidity, pressure);
if (nextHandler != null) {
nextHandler.handleRequest(temperature, humidity, pressure);
}
}
@Override
public void handleRequest(float temperature, float humidity, float pressure) {
// 具体处理逻辑
}
}
七、模式对比
在设计模式的大家族中,观察者模式和其他一些模式在某些方面存在相似性,但又有着各自独特的特点。这里我们对比一下观察者模式与中介者模式、策略模式,帮助大家更好地区分和选择使用。
| 模式 | 核心差异 | 适用场景 |
|---|---|---|
| 观察者 | 一对多依赖,状态变化触发通知 | 实时联动场景 |
| 中介者 | 集中协调对象交互 | 复杂系统解耦 |
| 策略 | 动态切换算法 | 支付方式选择 |
观察者模式与中介者模式:两者都用于处理对象间交互。观察者模式中,被观察者和观察者松耦合,被观察者状态变化时主动通知观察者;中介者模式则是通过中介者对象,将网状的对象交互关系变为星形,所有对象通过中介者间接交互,降低对象间耦合。例如在游戏开发中,角色状态变化通知队友(观察者模式);而不同游戏模块(如角色、场景、音效)通过游戏管理器(中介者)协调交互。
观察者模式与策略模式:观察者模式关注对象间的状态通知和依赖关系,而策略模式主要用于封装一系列可互换的算法,根据不同条件选择不同策略。以电商系统为例,订单状态变化通知物流、库存(观察者模式);支付时选择支付宝、微信等不同支付策略(策略模式)。
八、总结与思考
观察者模式作为一种常用的行为型设计模式,为实现对象间的联动和状态通知提供了优雅的解决方案。它适用于需要实时响应状态变化的场景,能有效降低对象之间的耦合度,并且支持动态扩展观察者。
在实际开发中,对于简单场景,可以直接使用 Java 内置的 Observer 类和 Observable 类,快速实现基本的观察者模式功能。而在复杂业务场景下,建议自定义实现,以满足更灵活、个性化的业务需求。同时,要注意平衡解耦带来的优势与性能开销,合理优化通知机制。
观察者模式在许多框架和系统中都有广泛应用,比如 Spring 的事件驱动机制(ApplicationEvent),本质上也是观察者模式的应用,通过事件发布和监听实现组件间的松耦合通信。
你在哪些项目中用过观察者模式呢?欢迎在评论区聊聊你的“联动”经验 👇💡

被折叠的 条评论
为什么被折叠?



