观察者模式(Observer)
观察者模式是最常用的行为型设计模式之一。它定义了对象之间的一对多依赖关系,当一个对象状态发生改变时,所有依赖它的对象都会收到通知并自动更新。
模式定义
观察者模式(Observer Pattern):定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。
核心概念
- Subject(主题/被观察者):被观察的对象,维护观察者列表,状态变化时通知观察者
- Observer(观察者):接收主题通知的对象,定义更新接口
问题场景
假设我们需要实现一个新闻订阅系统,用户可以订阅不同类型的新闻:
// 问题代码:直接依赖导致耦合
public class NewsAgency {
private List<User> users = new ArrayList<>();
private String news;
public void addNews(String news) {
this.news = news;
// 直接调用每个用户的方法
for (User user : users) {
user.receiveNews(news);
}
}
public void addUser(User user) {
users.add(user);
}
}
public class User {
private String name;
public User(String name) {
this.name = name;
}
public void receiveNews(String news) {
System.out.println(name + " 收到新闻: " + news);
}
}
// 问题:
// 1. NewsAgency 必须知道 User 的具体类
// 2. 新增其他类型的观察者需要修改 NewsAgency
// 3. 用户和新闻机构紧耦合
解决方案
使用观察者模式,将主题和观察者解耦:
// 观察者接口
public interface Observer {
void update(String message);
}
// 主题接口
public interface Subject {
void attach(Observer observer);
void detach(Observer observer);
void notifyObservers();
}
// 具体主题
public class NewsAgency implements Subject {
private List<Observer> observers = new ArrayList<>();
private String latestNews;
@Override
public void attach(Observer observer) {
observers.add(observer);
}
@Override
public void detach(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(latestNews);
}
}
public void publishNews(String news) {
this.latestNews = news;
System.out.println("\n=== 发布新闻: " + news + " ===");
notifyObservers();
}
}
// 具体观察者 - 邮件订阅者
public class EmailSubscriber implements Observer {
private String email;
public EmailSubscriber(String email) {
this.email = email;
}
@Override
public void update(String message) {
System.out.println("发送邮件到 " + email + ": " + message);
}
}
// 具体观察者 - 短信订阅者
public class SmsSubscriber implements Observer {
private String phone;
public SmsSubscriber(String phone) {
this.phone = phone;
}
@Override
public void update(String message) {
System.out.println("发送短信到 " + phone + ": " + message);
}
}
// 具体观察者 - App 推送
public class AppSubscriber implements Observer {
private String userId;
public AppSubscriber(String userId) {
this.userId = userId;
}
@Override
public void update(String message) {
System.out.println("App 推送到用户 " + userId + ": " + message);
}
}
// 客户端使用
public class Client {
public static void main(String[] args) {
NewsAgency agency = new NewsAgency();
// 添加订阅者
Observer emailUser = new EmailSubscriber("[email protected]");
Observer smsUser = new SmsSubscriber("13800138000");
Observer appUser = new AppSubscriber("user_123");
agency.attach(emailUser);
agency.attach(smsUser);
agency.attach(appUser);
// 发布新闻
agency.publishNews("重大新闻:设计模式教程更新了!");
// 取消订阅
agency.detach(smsUser);
// 发布另一条新闻
agency.publishNews("最新消息:观察者模式示例完成!");
}
}
输出:
=== 发布新闻: 重大新闻:设计模式教程更新了! ===
发送邮件到 [email protected]: 重大新闻:设计模式教程更新了!
发送短信到 13800138000: 重大新闻:设计模式教程更新了!
App 推送到用户 user_123: 重大新闻:设计模式教程更新了!
=== 发布新闻: 最新消息:观察者模式示例完成! ===
发送邮件到 [email protected]: 最新消息:观察者模式示例完成!
App 推送到用户 user_123: 最新消息:观察者模式示例完成!
模式结构
组成部分
- Subject(主题):被观察的对象,维护观察者列表,提供注册和删除观察者的方法
- Observer(观察者):定义更新接口,收到主题通知时调用
- ConcreteSubject(具体主题):实现 Subject 接口,状态改变时通知观察者
- ConcreteObserver(具体观察者):实现 Observer 接口,定义收到通知后的行为
推模型 vs 拉模型
推模型
主题主动将数据推送给观察者:
// 推模型:主题传递具体数据
public interface Observer {
void update(String news, String category, Date publishTime);
}
public class NewsAgency implements Subject {
private String news;
private String category;
private Date publishTime;
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(news, category, publishTime); // 推送所有数据
}
}
}
拉模型
观察者主动从主题拉取数据:
// 拉模型:观察者自己获取需要的数据
public interface Observer {
void update(Subject subject);
}
public class EmailSubscriber implements Observer {
public void update(Subject subject) {
// 观察者自己决定获取什么数据
String news = subject.getNews();
String category = subject.getCategory();
System.out.println("收到新闻: " + news);
}
}
对比:
| 特性 | 推模型 | 拉模型 |
|---|---|---|
| 数据获取 | 主题推送 | 观察者拉取 |
| 灵活性 | 较低 | 较高 |
| 效率 | 高(直接传数据) | 低(需要调用 getter) |
| 扩展性 | 低(新增数据需改接口) | 高(观察者自主决定) |
Java 内置观察者模式
Java 提供了内置的观察者模式支持:
import java.util.Observable;
import java.util.Observer;
// 具体主题(继承 Observable)
public class NewsAgency extends Observable {
private String news;
public void setNews(String news) {
this.news = news;
setChanged(); // 标记状态已改变
notifyObservers(news); // 通知观察者
}
public String getNews() {
return news;
}
}
// 具体观察者(实现 Observer)
public class EmailSubscriber implements Observer {
private String email;
public EmailSubscriber(String email) {
this.email = email;
}
@Override
public void update(Observable o, Object arg) {
System.out.println("邮件通知 " + email + ": " + arg);
}
}
// 使用
public class Main {
public static void main(String[] args) {
NewsAgency agency = new NewsAgency();
agency.addObserver(new EmailSubscriber("[email protected]"));
agency.setNews("Java 内置观察者模式示例");
}
}
注意
Java 9 中 Observable 和 Observer 已被标记为过时。建议使用 java.beans.PropertyChangeListener 或自定义实现。
实际应用案例
1. 事件监听器
// 按钮点击事件
public interface ClickListener {
void onClick(Event event);
}
public class Button {
private List<ClickListener> listeners = new ArrayList<>();
public void addClickListener(ClickListener listener) {
listeners.add(listener);
}
public void click() {
Event event = new Event(this, System.currentTimeMillis());
for (ClickListener listener : listeners) {
listener.onClick(event);
}
}
}
// 使用
Button button = new Button();
button.addClickListener(event -> System.out.println("按钮被点击了!"));
button.click();
2. 股票价格监控
public interface StockObserver {
void onPriceChange(String symbol, double oldPrice, double newPrice);
}
public class Stock {
private String symbol;
private double price;
private List<StockObserver> observers = new ArrayList<>();
public Stock(String symbol, double price) {
this.symbol = symbol;
this.price = price;
}
public void addObserver(StockObserver observer) {
observers.add(observer);
}
public void setPrice(double newPrice) {
double oldPrice = this.price;
this.price = newPrice;
notifyObservers(oldPrice, newPrice);
}
private void notifyObservers(double oldPrice, double newPrice) {
for (StockObserver observer : observers) {
observer.onPriceChange(symbol, oldPrice, newPrice);
}
}
}
// 投资者观察股票价格
public class Investor implements StockObserver {
private String name;
public Investor(String name) {
this.name = name;
}
@Override
public void onPriceChange(String symbol, double oldPrice, double newPrice) {
if (newPrice > oldPrice * 1.1) {
System.out.println(name + ": " + symbol + " 涨幅超过 10%,考虑卖出");
} else if (newPrice < oldPrice * 0.9) {
System.out.println(name + ": " + symbol + " 跌幅超过 10%,考虑买入");
}
}
}
3. Spring 事件机制
// Spring 的事件机制是观察者模式的应用
@Component
public class OrderEventListener {
@EventListener
public void onOrderCreated(OrderCreatedEvent event) {
System.out.println("收到订单创建事件: " + event.getOrderId());
}
@EventListener
public void onOrderPaid(OrderPaidEvent event) {
System.out.println("收到订单支付事件: " + event.getOrderId());
}
}
// 发布事件
@Service
public class OrderService {
@Autowired
private ApplicationEventPublisher eventPublisher;
public void createOrder(Order order) {
// 创建订单逻辑
eventPublisher.publishEvent(new OrderCreatedEvent(order.getId()));
}
}
优缺点分析
优点
- 松耦合:主题和观察者之间是松耦合的
- 开闭原则:新增观察者无需修改主题代码
- 广播通信:一个消息可以通知多个观察者
- 动态关系:可以在运行时建立和解除观察关系
缺点
- 性能问题:观察者过多时,通知所有观察者耗时
- 循环依赖:可能导致循环调用
- 顺序问题:观察者执行顺序不确定
- 调试困难:异步通知时难以追踪
最佳实践
1. 使用弱引用避免内存泄漏
import java.lang.ref.WeakReference;
public class NewsAgency {
private List<WeakReference<Observer>> observers = new ArrayList<>();
public void attach(Observer observer) {
observers.add(new WeakReference<>(observer));
}
public void notifyObservers() {
Iterator<WeakReference<Observer>> it = observers.iterator();
while (it.hasNext()) {
Observer observer = it.next().get();
if (observer == null) {
it.remove(); // 清理已回收的观察者
} else {
observer.update(message);
}
}
}
}
2. 异步通知提高性能
public class AsyncNewsAgency implements Subject {
private ExecutorService executor = Executors.newCachedThreadPool();
private List<Observer> observers = new ArrayList<>();
public void notifyObservers() {
for (Observer observer : observers) {
executor.submit(() -> observer.update(message));
}
}
}
3. 事件总线模式
// 简化的事件总线实现
public class EventBus {
private Map<Class<?>, List<Consumer<?>>> handlers = new ConcurrentHashMap<>();
public <T> void register(Class<T> eventType, Consumer<T> handler) {
handlers.computeIfAbsent(eventType, k -> new ArrayList<>()).add(handler);
}
@SuppressWarnings("unchecked")
public <T> void post(T event) {
List<Consumer<?>> eventHandlers = handlers.get(event.getClass());
if (eventHandlers != null) {
for (Consumer<?> handler : eventHandlers) {
((Consumer<T>) handler).accept(event);
}
}
}
}
与其他模式的关系
| 模式 | 关系 |
|---|---|
| 中介者模式 | 中介者封装对象间的通信,观察者直接通信 |
| 发布-订阅模式 | 发布-订阅模式是观察者模式的变体,增加消息队列 |
| 状态模式 | 状态模式中状态变化可以触发观察者通知 |
小结
观察者模式是一种简单但强大的行为型模式:
- 核心思想:定义一对多的依赖关系
- 主要优势:松耦合、开闭原则、广播通信
- 典型应用:事件监听、消息订阅、数据绑定
使用观察者模式时,注意:
- 避免循环依赖
- 注意性能问题
- 考虑使用弱引用避免内存泄漏
- 大规模场景考虑使用事件总线
练习
- 实现一个简单的消息发布-订阅系统
- 使用观察者模式实现一个温度监控系统
- 比较推模型和拉模型的优缺点
- 实现一个支持异步通知的观察者模式
参考资源
- 《设计模式:可复用面向对象软件的基础》
- 《Head First 设计模式》
- Java Observer 文档
- Spring Event 文档