观察者模式,从微信订阅到Spring事件,一文掌握解耦利器

你有没有想过,为什么微信公众号一发文章,所有关注的人都能立刻收到推送?为什么股票价格一变化,所有股民的APP都会实时更新?这背后的魔法就是【观察者模式】!一个让代码彻底告别"牵一发动全身"的解耦神器!

🎯 概念篇:什么是观察者模式?

观察者模式的本质

观察者模式就像现实生活中的"订阅关系":

  • 微信公众号:你关注了某个号,它一发文章你就收到通知
  • 报纸订阅:你订阅了报纸,每天早上送报员就会把报纸送到你家
  • 股票软件:你关注了某只股票,价格一变化就会推送给你
  • 直播间:主播一开播,所有关注的粉丝都会收到通知

在编程世界里,观察者模式就是这样的"订阅通知"机制:当一个对象(被观察者)的状态发生改变时,所有依赖于它的对象(观察者)都会得到通知并自动更新

核心角色

观察者模式有四个核心角色:

  1. 抽象被观察者(Subject):定义了添加、删除、通知观察者的方法
  2. 具体被观察者(ConcreteSubject):实现抽象被观察者,维护观察者列表
  3. 抽象观察者(Observer):定义了更新方法
  4. 具体观察者(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事件、消息队列、响应式编程
  • ⚠️ 注意事项:避免内存泄漏,控制观察者数量,处理异常情况

掌握观察者模式,让你的代码告别"牵一发动全身"的尴尬,拥抱松耦合的优雅架构!如果觉得有用,别忘了点赞收藏哦~ 🎯

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

慢德

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值