一、原理及通俗理解
观察者模式是一种行为设计模式,它定义了对象之间的一对多依赖关系,使得一个对象的状态发生变化时,其所有依赖者都会收到通知并自动更新。
观察者模式涉及两个主要角色:Subject(主题)和Observer(观察者)。
- Subject:维护一个观察者列表,提供注册和移除观察者的方法,并在自身状态发生变化时,通知所有已注册的观察者。
- Observer:对感兴趣的事物(Subject)的状态变化做出相应的反应。
就像订阅了某个博客的通知一样,当博客作者发布新文章时,所有订阅者都会收到通知。在这里,博客作者就是主题,而订阅者则是观察者。
二、股票项目中使用
需求描述:假设我们有一个股票交易系统,需要实现对股票价格变化的实时通知功能。当股票价格发生变化时,所有订阅该股票的用户应该能够接收到最新的股票价格信息。
- 定义主题接口(Subject) 包含注册观察者、移除观察者和通知观察者的方法
import java.util.ArrayList;
import java.util.List;
public interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers(String stockSymbol, double newPrice);
}
- 定义观察者接口(Observer) 包含更新股票价格的方法。
public interface Observer {
void update(String stockSymbol, double newPrice);
}
- 实现具体的主题(StockExchange)
StockExchange
类实现了Subject
接口,扮演股票交易所的角色。它维护了一个观察者列表、股票列表和股票价格列表。registerObserver
和removeObserver
方法用于注册和移除观察者。股票价格变化notifyObservers
方法通知所有已注册的观察者。
public class StockExchange implements Subject {
private List<Observer> observers; // 观察者列表
private List<String> stocks; // 股票列表
private List<Double> prices; // 股票价格
public StockExchange() {
observers = new ArrayList<>();
stocks = new ArrayList<>();
prices = new ArrayList<>();
}
//注册观察者
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
//移除观察者
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
//股票价格变化,通知所有已注册的观察者。
@Override
public void notifyObservers(String stockSymbol, double newPrice) {
for (Observer observer : observers) {
observer.update(stockSymbol, newPrice);
}
}
// 添加新的股票和初始价格
public void addStock(String stockSymbol, double initialPrice) {
stocks.add(stockSymbol);
prices.add(initialPrice);
}
// 更新股票价格并通知观察者
public void updateStockPrice(String stockSymbol, double newPrice) {
int index = stocks.indexOf(stockSymbol);
if (index != -1) {
prices.set(index, newPrice);
notifyObservers(stockSymbol, newPrice);
} else {
System.out.println("Stock not found: " + stockSymbol);
}
}
}
- 实现具体的观察者(InvestorClient)
InvestorClient
类实现了Observer
接口,扮演投资者的角色。它会在收到股票价格更新时打印出通知信息。
public class InvestorClient implements Observer {
private String investorName;
public InvestorClient(String investorName) {
this.investorName = investorName;
}
//股票价格更新时打印出通知信息。
@Override
public void update(String stockSymbol, double newPrice) {
System.out.println("Investor " + investorName + " received update: " + stockSymbol + " new price is $" + newPrice);
}
}
- 使用示例 在
main
方法中,我们创建了一个StockExchange
实例,添加了一些股票。然后创建了两个InvestorClient
实例并注册为观察者。
public class StockTradingApp {
public static void main(String[] args) {
// 创建股票交易所
StockExchange exchange = new StockExchange();
// 添加股票
exchange.addStock("AAPL", 120.0);
exchange.addStock("GOOG", 2500.0);
exchange.addStock("MSFT", 280.0);
// 创建投资者客户端
InvestorClient investor1 = new InvestorClient("John");
InvestorClient investor2 = new InvestorClient("Jane");
// 注册观察者
exchange.registerObserver(investor1);
exchange.registerObserver(investor2);
// 更新股票价格
exchange.updateStockPrice("AAPL", 125.5);
exchange.updateStockPrice("GOOG", 2520.0);
// 移除观察者
exchange.removeObserver(investor2);
// 再次更新股票价格
exchange.updateStockPrice("MSFT", 290.0);
}
}
- 输出: 模拟了几次股票价格更新,观察者会收到相应的通知,最后,我们移除了一个观察者,再次更新股票价格时,只有剩余的观察者会收到通知。
Investor John received update: AAPL new price is $125.5
Investor Jane received update: AAPL new price is $125.5
Investor John received update: GOOG new price is $2520.0
Investor Jane received update: GOOG new price is $2520.0
Investor John received update: MSFT new price is $290.0
三、Spring框架中使用观察者模式
- 在 Spring 的事件机制中,主要涉及以下几个核心组件:
- ApplicationEvent: 事件的基类,扮演观察者模式中的主题(Subject)角色。
- ApplicationListener:事件监听器接口,扮演观察者(Observer)角色,用于处理事件。
- ApplicationEventMulticaster:事件广播器,负责将事件分发给注册的监听器。
- 主题角色,
ApplicationEvent
继承自EventObject
类,EventObject
就是观察者模式中的主题,它维护了一个 source 属性,表示事件源,即产生事件的对象。
public abstract class ApplicationEvent extends EventObject {
// ...
/** Helper constructor for subclasses. */
public ApplicationEvent(Object source) {
super(source);
}
// ...
}
- 观察者角色,
ApplicationListener
接口扩展自EventListener
,它定义了一个onApplicationEvent
方法,用于处理传入的事件。每个事件监听器都需要实现这个接口,并编写处理逻辑。
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E event);
}
ApplicationEventMulticaster
接口定义了multicastEvent
方法,用于将事件广播给所有注册的监听器。
public interface ApplicationEventMulticaster {
void multicastEvent(ApplicationEvent event);
void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType);
// ...
}
- Spring 提供了一个默认实现
SimpleApplicationEventMulticaster
,它维护了一个ApplicationListener
列表,在接收到事件时,会遍历这个列表,依次通知每个监听器。
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
// ...
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
Executor executor = getTaskExecutor();
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
// ...
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
} else {
invokeListener(listener, event);
}
}
}
private void invokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
listener.onApplicationEvent(event);
}
catch (ClassCastException ex) {
// ...
}
}
// ...
}
7.在Spring框架中,观察者模式被广泛应用于事件监听和发布/订阅机制。使用观察者模式来实现事件驱动的编程,允许不同组件之间进行松耦合的通信。Spring中的事件(Event)是指应用程序中发生的某种特定状态的变化,比如应用启动、上下文刷新、HTTP请求处理等。当这些事件发生时,可以通过观察者模式将相关的监听器得到通知并做出响应。
- Spring 容器在初始化和销毁时会发布相应的事件,比如 ContextRefreshedEvent 和 ContextClosedEvent。
- Spring MVC 在处理请求过程中会发布 RequestHandledEvent 等事件。
- Spring Data 在执行数据库操作时会发布 AfterSaveEvent 等事件。
- 可以自定义事件和监听器,并将其注册到 Spring 容器中,实现自己的事件驱动逻辑。通过观察者模式,Spring 为我们提供了一种非常灵活和可扩展的编程模型。
四、总结优缺点以及使用经验
一、优点
- 解耦合:观察者模式将主题和观察者解耦,主题不需要知道观察者的具体实现,只需要维护一个观察者列表并通知它们。
- 可扩展性好:可以方便地添加新的观察者,而无需修改主题代码。
- 支持广播通信:主题可以向多个观察者对象发送通知,实现一对多的通信模式。
二、缺点
- 有可能出现循环依赖的问题:如果单个观察者对象被注册到一个或多个主题对象中,当主题状态发生变化时,可能会导致循环调用,使程序陷入死循环。
- 误操作风险:观察者需要正确地维护主题和观察者之间的注册关系,否则可能会出现观察者无法接收到通知或接收到重复通知的情况。
三、使用经验
- 适当使用:观察者模式适用于主题和观察者之间存在一对多的依赖关系,且需要实现自动通知机制的场景。但如果关系是一对一的,或者通知机制不是必需的,则不需要使用观察者模式,可以考虑使用其他设计模式。
- 维护注册关系:注意正确地注册和移除观察者,防止出现观察者无法接收到通知或接收到重复通知的情况。
- 避免循环依赖:如果存在循环依赖的风险,需要在设计时加以考虑,避免出现死循环的情况。
- 合理分配职责:主题负责维护观察者列表和通知,观察者负责响应通知并执行相应的操作。不要在主题或观察者中加入过多的逻辑,保持职责分离。
- 考虑性能:如果观察者数量较多,通知所有观察者可能会带来一定的性能开销。在必要时,可以考虑使用异步通知或者引入事件队列等机制来优化性能。