基本概念
行为型模式
设计模式分为以下三种:
- 创建型模式:核心解决对象的创建问题,如工厂模式、单例模式等;
- 结构型模式:专注对象的组合问题,如代理模式、适配器模式;
- 行为型模式:负责规范对象间的交互方式,如模板方法、命令模式、订阅发布模式。
行为型设计模式,主要用于规范对象之间的交互方式、划分对象职责、封装行为与算法,聚焦运行时对象的通信、行为流转、状态变化、算法替换,解决不同对象如何协同完成业务逻辑的问题,既包含多对象协作,也包含单一对象的行为管控。
观察者模式
观察者模式,俗称发布-订阅模式,该模式定义了一对多的依赖关系,多个观察者对象监听同一个主题对象。当主题对象状态发生改变时,会主动通知所有已注册的观察者,观察者接收通知后自动执行更新逻辑。

角色与示例
角色
观察者模式中应包含以下角色:
- 抽象主题:提供增删观察者对象的接口方法;
- 具体主题:实现增删方法,声明观察者类型列表;
- 抽象观察者:定义更新接口,监听主题更改时更新自身;
- 具体观察者:实现抽象观察者接口。
代码结构为:
- 观察者定义更新方法,接受消息时更新自身进行处理;
- 主题类聚合观察者,提供增删及观察者的通知方法;
- 聚合的观察者通常为列表,增删方法用于维护观察者,通知则是遍历列表集合,分别调用其update方法。
示例------微信公众号推送
微信公众号是经典的发布订阅模式体现,公众号更新时,新内容推送给关注公众号的用户端,公众号为主题,用户端为观察者,整体类图如下:

观察者代码:
java
// 观察者接口
public interface Observer {
// 观察者更新自身
void update(String message);
}
public class WeiXinUser implements Observer{
private String name;
public WeiXinUser(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println("WeiXinUser: " + this.name + "收到推文 " +message);
}
}
主题代码:
java
// 主题接口
public interface Subject {
// 增加观察者
void attach(Observer observer);
// 删除观察者
void detach(Observer observer);
// 更新通知观察者
void notifyObservers(String message);
}
import java.util.ArrayList;
public class SubscriptionSubject implements Subject{
ArrayList<Observer> observers = new ArrayList<Observer>();
@Override
public void attach(Observer observer) {
observers.add(observer);
}
@Override
public void detach(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers(String message) {
for (Observer observer : observers) {
observer.update(message);
}
}
}
使用如下代码进行测试:
java
public static void main(String[] args) {
SubscriptionSubject subject = new SubscriptionSubject();
subject.attach(new WeiXinUser("张三"));
subject.attach(new WeiXinUser("李四"));
subject.attach(new WeiXinUser("王五"));
subject.notifyObservers("《震惊!竟然有人2026年还手搓代码?》");
}
输出为:
WeiXinUser: 张三收到推文 《震惊!竟然有人2026年还手搓代码?》
WeiXinUser: 李四收到推文 《震惊!竟然有人2026年还手搓代码?》
WeiXinUser: 王五收到推文 《震惊!竟然有人2026年还手搓代码?》
模式分析
优缺点
优点:
- 降低耦合关系,主题虽聚合观察者,但为抽象耦合;
- 可实现广播机制。
缺点:
- 观察者较多时,逐个通知耗时较长;
- 有循环依赖时会产生循环调用,导致系统崩溃,需梳理依赖关系。
使用场景
适用于对象间存在一对多关系,一个改变影响其他对象,或抽象模型的两个方面,一个方面依赖于另一方面的场景。
工程实践
JDK实现
JDK提供了观察者模式的实现方法:
-
主题为
java.util.Observable,该类通过Vector作为观察者列表,提供add、delete方法用于增删观察者,notify方法用于通知,由以下源码,后加入的观察者会被先通知:javaif (!changed) return; // 列表转为数组 arrLocal = obs.toArray(); clearChanged(); } // 倒序通知 for (int i = arrLocal.length-1; i>=0; i--) ((Observer)arrLocal[i]).update(this, arg);使用方法如下:
javaimport java.util.Observable; // 被观察者:商品 class Product extends Observable { private String name; private double price; public Product(String name, double price) { this.name = name; this.price = price; } // 改价格 → 通知所有观察者 public void setPrice(double newPrice) { this.price = newPrice; setChanged(); // 标记:状态变了(必须!) notifyObservers(price); // 把新价格当 arg 传过去 } public double getPrice() { return price; } public String getName() { return name; } } -
观察者为
java.util.Observer,具体观察者实现该方法即可,示例代码如下:javaimport java.util.Observer; import java.util.Observable; // 观察者:用户 class User implements Observer { private String userName; public User(String userName) { this.userName = userName; } // 回调:主题变了 → 自动调用 update @Override public void update(Observable o, Object arg) { // o:是谁通知我的?(那个 Product) // arg:它捎来的数据(新价格) if (o instanceof Product) { Product p = (Product) o; double newPrice = (Double) arg; System.out.println(userName + " 收到:" + p.getName() + " 降价了!新价格:" + newPrice); } } }
相比前面手工推送的方法,JDK实现主要有以下三点不同:
- 设置changed标识进行手动确认 :
notify通常结合set方法使用,每次修改都自动同步,可通过该标识结合判断逻辑减小通知开销,示例代码如下:
java
public void setPrice(double p) {
if (Math.abs(p - this.price) > 0.01) { // 业务判断:才算真变化
this.price = p;
setChanged(); // ✅ 手动标记:我变了,需要通知
}
notifyObservers(price); // 内部会检查 changed,没 set 就不发
}
-
obs.toArray() 转数组保证线程安全 :
toArray把当前时刻的观察者列表vector复制一份到新数组(获取的是引用副本),避免其他线程增删观察者造成不一致影响,同时锁只在拷贝过程中生效,提高并发效率。 -
倒序通知
遗留设计,早期正序通知后删除观察者会导致
Vector大小发生变化,倒序则不会影响前面还没遍历到的下标,但目前使用array数组副本,不会对业务造成影响。
Spring实现
Spring中该模式称为事件驱动,在JDK的基础上作了进一步封装,用户只需指定事件(主题)、监听者、发布者即可,代码层面主题与监听者不直接相关,简单示例如下:
java
// 继承ApplicationEvent类,形成主题事件
public class Event extends ApplicationEvent {
private String message;
public Event(Object source,String message) {
super(source);
this.message = message;
}
public String getMessage() {
return this.message;
}
}
@Component
public class Listener {
// 监听器处理事件
@EventListener
public void dealEvent(Event event){
System.out.println("监听器收到消息:"+event.getMessage());
}
}
@Component
public class Publisher {
@Autowired
private ApplicationEventPublisher eventPublisher;
// 发布事件
public void publishEvent(String message){
eventPublisher.publishEvent(new Event(this,message));
}
}
// 实现CommandLineRunner接口,Spring启动自动执行run方法
@Component
public class PublisherTest implements CommandLineRunner {
@Autowired // 直接从 Spring 拿
private Publisher publisher;
@Override
public void run(String... args) throws Exception {
publisher.publishEvent("Hello World");
}
}
启动类启动后可在控制台看见如下输出:
监听器收到消息:Hello World
整体流程如下图:

该模式下
Event仅作为数据载体,发布由Publisher完成,底层通过Spring广播器Multicaster实现。相比JDK原生循环推送的方式,Spring引入了
Publisher,有以下三点优势:
- 解耦发布逻辑和业务逻辑 ,业务代码只需要调用
publisher.publishEvent(event),完全不感知监听者是谁、有多少、如何处理;- 发布逻辑Spring统一管理,事件交给Spring广播器,无需手写循环与线程调度;
- 避免业务代码直接持有监听器引用,避免业务代码直接持有所有监听器的引用,新增/删除监听器无需修改发布方代码,符合开闭原则。
有关EventListener注解需要进一步说明:
@EventListener自动按参数类型匹配@EventListener(classes = {UserEvent.class, OrderEvent.class})指定监听多个事件@EventListener(condition = "#event.message.contains('test')")条件过滤,只监听 message 包含 "test" 的事件- 额外增加
@Async,异步执行,@Async("线程池")可使用线程池异步执行,否则在主线程执行 @Order(1)指定多个监听器的执行顺序
总结
本文介绍了行为型设计模式的观察者模式,核心是主题变化时自动同步更新到观察者,但严格意义上观察者模式 ≠ 发布 - 订阅模式,二者思想同源但架构不同:
- 观察者模式:运行于同一应用内,主题与观察者存在间接依赖,主题状态变更后直接通知所有观察者,属于松耦合。代表技术:JDK Observable、Spring 事件驱动。
- 发布 - 订阅模式:通过中间件实现完全解耦,发布者与订阅者不直接通信、互不感知,支持跨服务、跨进程、跨机器。代表技术:RabbitMQ、Kafka、Redis 发布订阅。
有关发布订阅模式的实际项目可见:英语四六级证书审核