观察者模式深度解析:实现松耦合的订阅-发布系统
在软件开发中,我们经常遇到这样的场景:当一个对象的状态发生变化时,需要自动通知其他对象,并且这些被通知的对象可能是不确定的。观察者模式(Observer Pattern)正是为解决这类问题而生的经典设计模式。
什么是观察者模式?
观察者模式是一种行为型设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当主题对象状态发生变化时,它会通知所有观察者对象,使它们能够自动更新。
核心角色
- Subject(主题):被观察的对象,维护观察者列表,提供注册和删除观察者的方法
- Observer(观察者):定义更新接口,用于在主题状态改变时接收通知
- ConcreteSubject(具体主题):具体被观察对象,状态改变时通知所有观察者
- ConcreteObserver(具体观察者):实现观察者接口,维护对具体主题的引用
观察者模式的实现方式
1. 推模型 vs 拉模型
观察者模式有两种主要的实现方式:
推模型:主题主动将详细的变化数据推送给观察者 拉模型:主题只通知观察者状态发生变化,观察者主动从主题拉取所需数据
2. 经典实现示例
下面我们通过一个新闻发布系统的例子来展示观察者模式的实现:
// 观察者接口
interface Observer {
void update(String news);
}
// 主题接口
interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}
// 具体主题 - 新闻发布器
class NewsPublisher implements Subject {
private List<Observer> observers = new ArrayList<>();
private String latestNews;
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(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("发布新闻: " + news);
notifyObservers();
}
}
// 具体观察者 - 邮件订阅者
class EmailSubscriber implements Observer {
private String name;
public EmailSubscriber(String name) {
this.name = name;
}
@Override
public void update(String news) {
System.out.println(name + " 收到邮件通知: " + news);
}
}
// 具体观察者 - 短信订阅者
class SMSSubscriber implements Observer {
private String phone;
public SMSSubscriber(String phone) {
this.phone = phone;
}
@Override
public void update(String news) {
System.out.println("向 " + phone + " 发送短信: " + news);
}
}
// 使用示例
public class ObserverPatternDemo {
public static void main(String[] args) {
NewsPublisher publisher = new NewsPublisher();
Observer emailSubscriber1 = new EmailSubscriber("张三");
Observer emailSubscriber2 = new EmailSubscriber("李四");
Observer smsSubscriber = new SMSSubscriber("13800138000");
publisher.registerObserver(emailSubscriber1);
publisher.registerObserver(emailSubscriber2);
publisher.registerObserver(smsSubscriber);
publisher.publishNews("Java 21 正式发布!");
// 输出:
// 发布新闻: Java 21 正式发布!
// 张三 收到邮件通知: Java 21 正式发布!
// 李四 收到邮件通知: Java 21 正式发布!
// 向 13800138000 发送短信: Java 21 正式发布!
}
}
现代实现:使用Java内置观察者
Java在java.util包中提供了内置的观察者支持:
import java.util.Observable;
import java.util.Observer;
// 使用Java内置Observable
class StockMarket extends Observable {
private String stockSymbol;
private double price;
public StockMarket(String stockSymbol, double price) {
this.stockSymbol = stockSymbol;
this.price = price;
}
public void setPrice(double price) {
if (this.price != price) {
this.price = price;
setChanged(); // 标记状态已改变
notifyObservers(price); // 通知观察者,传递价格数据
}
}
public double getPrice() {
return price;
}
public String getStockSymbol() {
return stockSymbol;
}
}
// 使用Java内置Observer
class StockTrader implements Observer {
private String name;
public StockTrader(String name) {
this.name = name;
}
@Override
public void update(Observable o, Object arg) {
if (o instanceof StockMarket) {
StockMarket market = (StockMarket) o;
double newPrice = (Double) arg;
System.out.println(name + " 收到通知: " + market.getStockSymbol() +
" 价格变为: " + newPrice);
// 根据价格变化做出交易决策
if (newPrice > 100) {
System.out.println(name + " 决定卖出");
} else if (newPrice < 50) {
System.out.println(name + " 决定买入");
}
}
}
}
// 使用示例
public class JavaBuiltInObserverDemo {
public static void main(String[] args) {
StockMarket appleStock = new StockMarket("AAPL", 75.0);
StockTrader trader1 = new StockTrader("王五");
StockTrader trader2 = new StockTrader("赵六");
appleStock.addObserver(trader1);
appleStock.addObserver(trader2);
// 模拟价格变化
appleStock.setPrice(80.0);
appleStock.setPrice(45.0);
appleStock.setPrice(120.0);
}
}
实际应用场景
1. GUI事件处理
在图形用户界面中,观察者模式被广泛应用:
// 简化版GUI事件处理
class Button {
private List<ActionListener> listeners = new ArrayList<>();
public void addActionListener(ActionListener listener) {
listeners.add(listener);
}
public void click() {
System.out.println("按钮被点击");
ActionEvent event = new ActionEvent(this, System.currentTimeMillis(), "click");
for (ActionListener listener : listeners) {
listener.actionPerformed(event);
}
}
}
interface ActionListener {
void actionPerformed(ActionEvent event);
}
class LoginAction implements ActionListener {
@Override
public void actionPerformed(ActionEvent event) {
System.out.println("执行登录操作");
}
}
class LogAction implements ActionListener {
@Override
public void actionPerformed(ActionEvent event) {
System.out.println("记录操作日志");
}
}
2. 消息队列系统
在分布式系统中,观察者模式常用于消息队列的实现:
// 简化的消息队列实现
class MessageQueue {
private Map<String, List<MessageConsumer>> topicConsumers = new HashMap<>();
public void subscribe(String topic, MessageConsumer consumer) {
topicConsumers.computeIfAbsent(topic, k -> new ArrayList<>()).add(consumer);
}
public void publish(String topic, String message) {
List<MessageConsumer> consumers = topicConsumers.get(topic);
if (consumers != null) {
for (MessageConsumer consumer : consumers) {
consumer.consume(topic, message);
}
}
}
}
interface MessageConsumer {
void consume(String topic, String message);
}
class OrderService implements MessageConsumer {
@Override
public void consume(String topic, String message) {
if ("order.created".equals(topic)) {
System.out.println("订单服务处理新订单: " + message);
}
}
}
class InventoryService implements MessageConsumer {
@Override
public void consume(String topic, String message) {
if ("order.created".equals(topic)) {
System.out.println("库存服务更新库存: " + message);
}
}
}
观察者模式的优缺点
优点
- 松耦合:主题和观察者之间抽象耦合,彼此不需要知道具体实现
- 扩展性:可以方便地增加新的观察者,符合开闭原则
- 广播通信:支持一对多的通信方式
- 符合依赖倒置原则:依赖于抽象而不是具体实现
缺点
- 内存泄漏风险:如果观察者没有正确注销,可能导致内存泄漏
- 通知顺序不确定:多个观察者的通知顺序可能影响系统行为
- 性能问题:如果观察者数量很多,通知过程可能比较耗时
最佳实践和注意事项
1. 避免循环依赖
// 错误的实现 - 可能导致循环依赖
class ProblematicObserver implements Observer {
private Subject subject;
public ProblematicObserver(Subject subject) {
this.subject = subject;
}
@Override
public void update(String data) {
// 在更新方法中又调用了主题的方法,可能导致循环调用
if (data.contains("special")) {
subject.someMethod(); // 危险的操作!
}
}
}
2. 使用弱引用避免内存泄漏
// 使用弱引用避免内存泄漏
class SafeSubject implements Subject {
private List<WeakReference<Observer>> observers = new ArrayList<>();
@Override
public void registerObserver(Observer observer) {
observers.add(new WeakReference<>(observer));
}
@Override
public void notifyObservers() {
Iterator<WeakReference<Observer>> iterator = observers.iterator();
while (iterator.hasNext()) {
WeakReference<Observer> ref = iterator.next();
Observer observer = ref.get();
if (observer != null) {
observer.update("some data");
} else {
iterator.remove(); // 清理已被GC回收的观察者
}
}
}
}
3. 异步通知提高性能
// 异步通知实现
class AsyncSubject implements Subject {
private List<Observer> observers = new ArrayList<>();
private ExecutorService executor = Executors.newCachedThreadPool();
@Override
public void notifyObservers() {
for (Observer observer : observers) {
executor.submit(() -> {
try {
observer.update("async data");
} catch (Exception e) {
// 处理异常,避免影响其他观察者
System.err.println("观察者处理异常: " + e.getMessage());
}
});
}
}
}
总结
观察者模式是构建松耦合、可扩展系统的强大工具。通过订阅-发布机制,它有效地解耦了对象之间的依赖关系,使得系统更加灵活和易于维护。在实际开发中,我们可以根据具体需求选择推模型或拉模型,也可以结合现代编程语言特性(如Java的Observable类)来简化实现。
无论你是构建GUI应用程序、消息系统,还是需要实现事件驱动的架构,观察者模式都能提供优雅的解决方案。只要注意避免常见陷阱(如内存泄漏、循环依赖等),这种模式将成为你工具箱中不可或缺的利器。
掌握观察者模式,不仅能够提升代码质量,更能帮助你以更加抽象和模块化的方式思考系统设计,这是成为优秀软件工程师的重要一步。