你有没有想过,为什么微信公众号一发文章,所有关注的人都能立刻收到推送?为什么股票价格一变化,所有股民的APP都会实时更新?这背后的魔法就是【观察者模式】!一个让代码彻底告别"牵一发动全身"的解耦神器!
🎯 概念篇:什么是观察者模式?
观察者模式的本质
观察者模式就像现实生活中的"订阅关系":
- 微信公众号:你关注了某个号,它一发文章你就收到通知
- 报纸订阅:你订阅了报纸,每天早上送报员就会把报纸送到你家
- 股票软件:你关注了某只股票,价格一变化就会推送给你
- 直播间:主播一开播,所有关注的粉丝都会收到通知
在编程世界里,观察者模式就是这样的"订阅通知"机制:当一个对象(被观察者)的状态发生改变时,所有依赖于它的对象(观察者)都会得到通知并自动更新。
核心角色
观察者模式有四个核心角色:
- 抽象被观察者(Subject):定义了添加、删除、通知观察者的方法
- 具体被观察者(ConcreteSubject):实现抽象被观察者,维护观察者列表
- 抽象观察者(Observer):定义了更新方法
- 具体观察者(ConcreteObserver):实现抽象观察者,定义具体的更新逻辑
简单来说:被观察者维护一个观察者列表,状态变化时通知所有观察者。
🚀 应用篇:观察者模式的使用场景
观察者模式在软件开发中有很多经典应用:
1. GUI事件处理
// 按钮点击事件
button.addClickListener(event -> {
System.out.println("按钮被点击了!");
});
2. 模型-视图(MVC)架构
// 数据模型变化时,自动更新所有视图
model.addObserver(view1);
model.addObserver(view2);
model.setData(newData); // 视图自动更新
3. 消息队列系统
// 订单状态变化,通知多个业务模块
orderStatusChanged -> {
notifyPaymentService();
notifyInventoryService();
notifyLogisticsService();
}
4. 缓存失效通知
// 数据库数据更新时,清除相关缓存
database.addObserver(cacheManager);
database.update(data); // 缓存自动失效
🔧 原理篇:代码实战
让我们用一个"股票价格监控"的例子来实现观察者模式:
import java.util.*;
// 1. 抽象观察者
interface StockObserver {
void update(String stockName, double price);
}
// 2. 抽象被观察者
abstract class StockSubject {
protected List<StockObserver> observers = new ArrayList<>();
// 添加观察者
public void addObserver(StockObserver observer) {
observers.add(observer);
System.out.println("新增观察者,当前观察者数量:" + observers.size());
}
// 移除观察者
public void removeObserver(StockObserver observer) {
observers.remove(observer);
System.out.println("移除观察者,当前观察者数量:" + observers.size());
}
// 通知所有观察者
protected void notifyObservers(String stockName, double price) {
System.out.println("=== 开始通知所有观察者 ===");
for (StockObserver observer : observers) {
observer.update(stockName, price);
}
System.out.println("=== 通知完成 ===");
}
}
// 3. 具体被观察者 - 股票价格监控器
class StockPriceMonitor extends StockSubject {
private String stockName;
private double currentPrice;
public StockPriceMonitor(String stockName) {
this.stockName = stockName;
}
// 更新股票价格(关键方法)
public void setPrice(double newPrice) {
if (this.currentPrice != newPrice) {
System.out.println("\n📈 " + stockName + " 价格发生变化:"
+ currentPrice + " → " + newPrice);
this.currentPrice = newPrice;
// 价格变化时通知所有观察者
notifyObservers(stockName, newPrice);
}
}
public double getCurrentPrice() {
return currentPrice;
}
}
// 4. 具体观察者 - 投资者
class Investor implements StockObserver {
private String name;
private double buyPrice; // 买入价格
public Investor(String name, double buyPrice) {
this.name = name;
this.buyPrice = buyPrice;
}
@Override
public void update(String stockName, double currentPrice) {
double profitRate = (currentPrice - buyPrice) / buyPrice * 100;
String status = profitRate >= 0 ? "💰 盈利" : "💸 亏损";
System.out.println("👤 投资者 " + name + " 收到通知:");
System.out.println(" 股票:" + stockName + ",当前价格:" + currentPrice);
System.out.println(" " + status + ":" + String.format("%.2f%%", Math.abs(profitRate)));
// 简单的交易策略
if (profitRate >= 20) {
System.out.println(" 🎉 涨幅超过20%,考虑卖出!");
} else if (profitRate <= -10) {
System.out.println(" ⚠️ 跌幅超过10%,考虑止损!");
}
System.out.println();
}
}
// 5. 具体观察者 - 交易系统
class TradingSystem implements StockObserver {
private String systemName;
public TradingSystem(String systemName) {
this.systemName = systemName;
}
@Override
public void update(String stockName, double currentPrice) {
System.out.println("🖥️ " + systemName + " 系统记录:");
System.out.println(" 时间:" + new Date());
System.out.println(" 股票:" + stockName + " 价格更新为:" + currentPrice);
System.out.println(" 已更新数据库和缓存");
System.out.println();
}
}
// 6. 使用示例
public class ObserverPatternDemo {
public static void main(String[] args) {
// 创建股票价格监控器
StockPriceMonitor appleStock = new StockPriceMonitor("AAPL");
// 创建观察者
Investor alice = new Investor("Alice", 150.0);
Investor bob = new Investor("Bob", 145.0);
TradingSystem system = new TradingSystem("华尔街交易系统");
// 注册观察者
appleStock.addObserver(alice);
appleStock.addObserver(bob);
appleStock.addObserver(system);
// 模拟股票价格变化
appleStock.setPrice(155.0); // 价格上涨
Thread.sleep(1000); // 暂停一下,模拟时间间隔
appleStock.setPrice(140.0); // 价格下跌
// 移除一个观察者
appleStock.removeObserver(bob);
appleStock.setPrice(160.0); // 再次变化,Bob不会收到通知
}
}
Java内置观察者模式
Java其实提供了内置的观察者模式支持:
import java.util.Observable;
import java.util.Observer;
// 使用Java内置的Observable类
class SimpleStockPrice extends Observable {
private double price;
public void setPrice(double newPrice) {
this.price = newPrice;
setChanged(); // 标记状态已改变
notifyObservers(newPrice); // 通知观察者
}
}
// 使用Java内置的Observer接口
class SimpleInvestor implements Observer {
private String name;
public SimpleInvestor(String name) {
this.name = name;
}
@Override
public void update(Observable o, Object arg) {
double price = (Double) arg;
System.out.println(name + " 收到价格更新:" + price);
}
}
观察者模式结构图
<svg width="600" height="350" xmlns="http://www.w3.org/2000/svg">
<!-- 抽象被观察者 -->
<rect x="50" y="50" width="120" height="80" fill="#e3f2fd" stroke="#1976d2" stroke-width="2"/>
<text x="110" y="75" text-anchor="middle" font-family="Arial" font-size="12" font-weight="bold">抽象被观察者</text>
<text x="110" y="90" text-anchor="middle" font-family="Arial" font-size="10">Subject</text>
<text x="60" y="105" font-family="Arial" font-size="9">+addObserver()</text>
<text x="60" y="118" font-family="Arial" font-size="9">+removeObserver()</text>
<!-- 具体被观察者 -->
<rect x="50" y="180" width="120" height="80" fill="#e8f5e8" stroke="#388e3c" stroke-width="2"/>
<text x="110" y="205" text-anchor="middle" font-family="Arial" font-size="12" font-weight="bold">具体被观察者</text>
<text x="110" y="220" text-anchor="middle" font-family="Arial" font-size="10">ConcreteSubject</text>
<text x="60" y="235" font-family="Arial" font-size="9">+setState()</text>
<text x="60" y="248" font-family="Arial" font-size="9">+notifyObservers()</text>
<!-- 抽象观察者 -->
<rect x="350" y="50" width="120" height="80" fill="#fff3e0" stroke="#f57c00" stroke-width="2"/>
<text x="410" y="75" text-anchor="middle" font-family="Arial" font-size="12" font-weight="bold">抽象观察者</text>
<text x="410" y="90" text-anchor="middle" font-family="Arial" font-size="10">Observer</text>
<text x="360" y="105" font-family="Arial" font-size="9">+update()</text>
<!-- 具体观察者1 -->
<rect x="270" y="180" width="120" height="80" fill="#fce4ec" stroke="#c2185b" stroke-width="2"/>
<text x="330" y="205" text-anchor="middle" font-family="Arial" font-size="12" font-weight="bold">具体观察者1</text>
<text x="330" y="220" text-anchor="middle" font-family="Arial" font-size="10">ConcreteObserver1</text>
<text x="280" y="235" font-family="Arial" font-size="9">+update()</text>
<!-- 具体观察者2 -->
<rect x="430" y="180" width="120" height="80" fill="#f3e5f5" stroke="#7b1fa2" stroke-width="2"/>
<text x="490" y="205" text-anchor="middle" font-family="Arial" font-size="12" font-weight="bold">具体观察者2</text>
<text x="490" y="220" text-anchor="middle" font-family="Arial" font-size="10">ConcreteObserver2</text>
<text x="440" y="235" font-family="Arial" font-size="9">+update()</text>
<!-- 关系线 -->
<!-- 继承关系 -->
<line x1="110" y1="130" x2="110" y2="180" stroke="#666" stroke-width="2" marker-end="url(#inheritance)"/>
<line x1="410" y1="130" x2="330" y2="180" stroke="#666" stroke-width="2" marker-end="url(#inheritance)"/>
<line x1="410" y1="130" x2="490" y2="180" stroke="#666" stroke-width="2" marker-end="url(#inheritance)"/>
<!-- 依赖关系 -->
<line x1="170" y1="90" x2="350" y2="90" stroke="#ff5722" stroke-width="2" stroke-dasharray="5,5" marker-end="url(#dependency)"/>
<text x="250" y="85" text-anchor="middle" font-family="Arial" font-size="10" fill="#ff5722">通知</text>
<!-- 观察者列表 -->
<line x1="170" y1="220" x2="270" y2="220" stroke="#2196f3" stroke-width="2" marker-end="url(#composition)"/>
<line x1="170" y1="230" x2="430" y2="230" stroke="#2196f3" stroke-width="2" marker-end="url(#composition)"/>
<text x="220" y="215" text-anchor="middle" font-family="Arial" font-size="10" fill="#2196f3">维护</text>
<!-- 箭头定义 -->
<defs>
<!-- 继承箭头 -->
<marker id="inheritance" markerWidth="12" markerHeight="10" refX="10" refY="5" orient="auto">
<polygon points="0 0, 12 5, 0 10" fill="none" stroke="#666" stroke-width="2"/>
</marker>
<!-- 依赖箭头 -->
<marker id="dependency" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#ff5722"/>
</marker>
<!-- 组合箭头 -->
<marker id="composition" markerWidth="12" markerHeight="8" refX="10" refY="4" orient="auto">
<polygon points="0 0, 8 4, 0 8, 12 4" fill="#2196f3"/>
</marker>
</defs>
</svg>
🔄 扩展篇:进阶应用
1. 事件驱动架构
观察者模式是事件驱动架构的基础:
// 事件总线实现
public class EventBus {
private Map<Class<?>, List<Object>> subscribers = new ConcurrentHashMap<>();
// 注册订阅者
public void register(Object subscriber) {
Method[] methods = subscriber.getClass().getDeclaredMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(Subscribe.class)) {
Class<?> eventType = method.getParameterTypes()[0];
subscribers.computeIfAbsent(eventType, k -> new ArrayList<>()).add(subscriber);
}
}
}
// 发布事件
public void post(Object event) {
List<Object> eventSubscribers = subscribers.get(event.getClass());
if (eventSubscribers != null) {
for (Object subscriber : eventSubscribers) {
try {
Method method = findSubscribeMethod(subscriber, event.getClass());
method.invoke(subscriber, event);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
// 使用注解标记订阅方法
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@interface Subscribe {
}
// 事件类
class OrderCreatedEvent {
private String orderId;
private double amount;
// 构造器和getter省略...
}
// 订阅者
class EmailService {
@Subscribe
public void handleOrderCreated(OrderCreatedEvent event) {
System.out.println("发送订单确认邮件:" + event.getOrderId());
}
}
class InventoryService {
@Subscribe
public void handleOrderCreated(OrderCreatedEvent event) {
System.out.println("更新库存:" + event.getOrderId());
}
}
2. Spring框架中的应用
Spring大量使用了观察者模式:
// Spring事件发布
@Component
public class OrderService {
@Autowired
private ApplicationEventPublisher eventPublisher;
public void createOrder(Order order) {
// 创建订单业务逻辑
saveOrder(order);
// 发布事件
eventPublisher.publishEvent(new OrderCreatedEvent(order));
}
}
// Spring事件监听
@Component
public class OrderEventListener {
@EventListener
public void handleOrderCreated(OrderCreatedEvent event) {
// 处理订单创建事件
System.out.println("订单创建成功:" + event.getOrder().getId());
}
@Async // 异步处理
@EventListener
public void sendNotification(OrderCreatedEvent event) {
// 发送通知
notificationService.send(event.getOrder());
}
}
观察者模式执行流程
<svg width="650" height="400" xmlns="http://www.w3.org/2000/svg">
<!-- 被观察者 -->
<rect x="50" y="50" width="100" height="60" fill="#e3f2fd" stroke="#1976d2" stroke-width="2"/>
<text x="100" y="75" text-anchor="middle" font-family="Arial" font-size="12" font-weight="bold">被观察者</text>
<text x="100" y="90" text-anchor="middle" font-family="Arial" font-size="10">Subject</text>
<!-- 观察者列表 -->
<rect x="250" y="50" width="80" height="40" fill="#e8f5e8" stroke="#388e3c" stroke-width="2"/>
<text x="290" y="75" text-anchor="middle" font-family="Arial" font-size="11" font-weight="bold">观察者列表</text>
<!-- 观察者们 -->
<rect x="450" y="30" width="80" height="40" fill="#fff3e0" stroke="#f57c00" stroke-width="2"/>
<text x="490" y="55" text-anchor="middle" font-family="Arial" font-size="11" font-weight="bold">观察者1</text>
<rect x="450" y="80" width="80" height="40" fill="#fff3e0" stroke="#f57c00" stroke-width="2"/>
<text x="490" y="105" text-anchor="middle" font-family="Arial" font-size="11" font-weight="bold">观察者2</text>
<rect x="450" y="130" width="80" height="40" fill="#fff3e0" stroke="#f57c00" stroke-width="2"/>
<text x="490" y="155" text-anchor="middle" font-family="Arial" font-size="11" font-weight="bold">观察者3</text>
<!-- 执行步骤 -->
<!-- 步骤1:状态改变 -->
<circle cx="100" cy="200" r="15" fill="#2196f3" stroke="#fff" stroke-width="2"/>
<text x="100" y="205" text-anchor="middle" font-family="Arial" font-size="12" font-weight="bold" fill="white">1</text>
<text x="100" y="230" text-anchor="middle" font-family="Arial" font-size="11" font-weight="bold">状态改变</text>
<!-- 步骤2:遍历观察者 -->
<circle cx="290" cy="200" r="15" fill="#4caf50" stroke="#fff" stroke-width="2"/>
<text x="290" y="205" text-anchor="middle" font-family="Arial" font-size="12" font-weight="bold" fill="white">2</text>
<text x="290" y="230" text-anchor="middle" font-family="Arial" font-size="11" font-weight="bold">遍历列表</text>
<!-- 步骤3:通知观察者 -->
<circle cx="490" cy="200" r="15" fill="#ff9800" stroke="#fff" stroke-width="2"/>
<text x="490" y="205" text-anchor="middle" font-family="Arial" font-size="12" font-weight="bold" fill="white">3</text>
<text x="490" y="230" text-anchor="middle" font-family="Arial" font-size="11" font-weight="bold">通知更新</text>
<!-- 连接线 -->
<!-- 被观察者到观察者列表 -->
<line x1="150" y1="80" x2="250" y2="70" stroke="#2196f3" stroke-width="2" marker-end="url(#arrow)"/>
<text x="190" y="70" text-anchor="middle" font-family="Arial" font-size="10" fill="#2196f3">维护</text>
<!-- 观察者列表到观察者 -->
<line x1="330" y1="60" x2="450" y2="50" stroke="#4caf50" stroke-width="2" marker-end="url(#arrow)"/>
<line x1="330" y1="70" x2="450" y2="100" stroke="#4caf50" stroke-width="2" marker-end="url(#arrow)"/>
<line x1="330" y1="80" x2="450" y2="150" stroke="#4caf50" stroke-width="2" marker-end="url(#arrow)"/>
<!-- 执行流程箭头 -->
<line x1="115" y1="200" x2="275" y2="200" stroke="#ff5722" stroke-width="3" marker-end="url(#arrow)"/>
<line x1="305" y1="200" x2="475" y2="200" stroke="#ff5722" stroke-width="3" marker-end="url(#arrow)"/>
<!-- 通知消息 -->
<line x1="490" y1="185" x2="490" y2="50" stroke="#e91e63" stroke-width="2" stroke-dasharray="3,3" marker-end="url(#arrow)"/>
<line x1="495" y1="185" x2="495" y2="100" stroke="#e91e63" stroke-width="2" stroke-dasharray="3,3" marker-end="url(#arrow)"/>
<line x1="485" y1="185" x2="485" y2="150" stroke="#e91e63" stroke-width="2" stroke-dasharray="3,3" marker-end="url(#arrow)"/>
<!-- 说明文字 -->
<text x="50" y="300" font-family="Arial" font-size="14" font-weight="bold">执行流程:</text>
<text x="50" y="320" font-family="Arial" font-size="12">1. 被观察者状态发生改变</text>
<text x="50" y="340" font-family="Arial" font-size="12">2. 遍历观察者列表</text>
<text x="50" y="360" font-family="Arial" font-size="12">3. 逐个调用观察者的update()方法</text>
<text x="50" y="380" font-family="Arial" font-size="12">4. 观察者接收通知并执行相应逻辑</text>
<!-- 箭头定义 -->
<defs>
<marker id="arrow" markerWidth="10" markerHeight="7" refX="9" refY="3.5" orient="auto">
<polygon points="0 0, 10 3.5, 0 7" fill="#666"/>
</marker>
</defs>
</svg>
3. 响应式编程
观察者模式也是响应式编程的基础:
// RxJava风格的观察者
Observable.fromArray(1, 2, 3, 4, 5)
.filter(x -> x > 2)
.map(x -> x * x)
.subscribe(
result -> System.out.println("结果: " + result),
error -> System.err.println("错误: " + error),
() -> System.out.println("完成!")
);
🎯 面试热点:常考问题解析
Q1:观察者模式的优缺点?
优点:
- ✅ 解耦合:被观察者和观察者之间松耦合
- ✅ 开放封闭原则:可以动态添加和删除观察者
- ✅ 广播通信:支持一对多的依赖关系
- ✅ 符合单一职责:每个观察者只关心自己的业务
缺点:
- ❌ 性能问题:观察者过多时,通知消耗时间较长
- ❌ 内存泄漏风险:观察者没有正确移除可能导致内存泄漏
- ❌ 循环依赖:观察者和被观察者之间可能出现循环引用
- ❌ 调试困难:间接调用使得调试复杂
Q2:观察者模式 vs 发布-订阅模式?
特性 | 观察者模式 | 发布-订阅模式 |
---|---|---|
耦合度 | 观察者和被观察者直接关联 | 通过事件总线解耦 |
通信方式 | 同步调用 | 可异步处理 |
实现复杂度 | 简单 | 相对复杂 |
扩展性 | 一般 | 更好 |
Q3:如何避免观察者模式的内存泄漏?
public class SafeObserverPattern {
private List<WeakReference<Observer>> observers = new ArrayList<>();
public void addObserver(Observer observer) {
observers.add(new WeakReference<>(observer));
}
public void notifyObservers() {
// 清理已被GC的观察者
observers.removeIf(ref -> ref.get() == null);
// 通知存活的观察者
for (WeakReference<Observer> ref : observers) {
Observer observer = ref.get();
if (observer != null) {
observer.update();
}
}
}
}
Q4:手写一个简单的观察者模式
public class SimpleObserver {
// 被观察者接口
interface Subject {
void addObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}
// 观察者接口
interface Observer {
void update(String message);
}
// 具体实现
static class NewsAgency implements Subject {
private List<Observer> observers = new ArrayList<>();
private String news;
public void addObserver(Observer observer) {
observers.add(observer);
}
public void removeObserver(Observer observer) {
observers.remove(observer);
}
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(news);
}
}
public void setNews(String news) {
this.news = news;
notifyObservers();
}
}
static class NewsChannel implements Observer {
private String name;
public NewsChannel(String name) {
this.name = name;
}
public void update(String news) {
System.out.println(name + " 收到新闻: " + news);
}
}
}
🎉 总结
观察者模式就像生活中的"订阅通知"机制,让我们的代码实现了优雅的解耦。从简单的GUI事件处理,到复杂的微服务事件驱动架构,观察者模式都发挥着重要作用。
记住这个口诀:一个变化通知多,观察模式解耦好!
关键要点:
- 🎯 核心思想:一对多的依赖关系,状态变化时自动通知
- 🔧 实现要素:被观察者维护观察者列表,变化时遍历通知
- 🚀 实际应用:Spring事件、GUI事件、消息队列、响应式编程
- ⚠️ 注意事项:避免内存泄漏,控制观察者数量,处理异常情况
掌握观察者模式,让你的代码告别"牵一发动全身"的尴尬,拥抱松耦合的优雅架构!如果觉得有用,别忘了点赞收藏哦~ 🎯