跳到主要内容

观察者模式(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: 最新消息:观察者模式示例完成!

模式结构

组成部分

  1. Subject(主题):被观察的对象,维护观察者列表,提供注册和删除观察者的方法
  2. Observer(观察者):定义更新接口,收到主题通知时调用
  3. ConcreteSubject(具体主题):实现 Subject 接口,状态改变时通知观察者
  4. 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 中 ObservableObserver 已被标记为过时。建议使用 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. 松耦合:主题和观察者之间是松耦合的
  2. 开闭原则:新增观察者无需修改主题代码
  3. 广播通信:一个消息可以通知多个观察者
  4. 动态关系:可以在运行时建立和解除观察关系

缺点

  1. 性能问题:观察者过多时,通知所有观察者耗时
  2. 循环依赖:可能导致循环调用
  3. 顺序问题:观察者执行顺序不确定
  4. 调试困难:异步通知时难以追踪

最佳实践

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);
}
}
}
}

与其他模式的关系

模式关系
中介者模式中介者封装对象间的通信,观察者直接通信
发布-订阅模式发布-订阅模式是观察者模式的变体,增加消息队列
状态模式状态模式中状态变化可以触发观察者通知

小结

观察者模式是一种简单但强大的行为型模式:

  1. 核心思想:定义一对多的依赖关系
  2. 主要优势:松耦合、开闭原则、广播通信
  3. 典型应用:事件监听、消息订阅、数据绑定

使用观察者模式时,注意:

  • 避免循环依赖
  • 注意性能问题
  • 考虑使用弱引用避免内存泄漏
  • 大规模场景考虑使用事件总线

练习

  1. 实现一个简单的消息发布-订阅系统
  2. 使用观察者模式实现一个温度监控系统
  3. 比较推模型和拉模型的优缺点
  4. 实现一个支持异步通知的观察者模式

参考资源