Skip to content

观察者模式(Observer Pattern) ​

一、模式概述 ​

观察者模式是一种行为型设计模式,它定义了对象之间的一对多依赖关系,当一个对象(主题/被观察者)状态改变时,所有依赖于它的对象(观察者)都会得到通知并自动更新。

核心思想:将观察者和被观察者解耦,观察者不需要知道被观察者的具体实现,只需要订阅即可。当被观察者状态变化时,自动通知所有观察者。

二、角色说明 ​

角色说明
Subject(主题/被观察者)持有观察者列表,状态变化时通知所有观察者
Observer(观察者)定义更新接口,接收主题的通知
ConcreteSubject(具体主题)存储状态,状态变化时通知所有观察者
ConcreteObserver(具体观察者)实现更新接口,根据通知更新自身状态

三、生活中的例子 ​

  • 微信公众号:你关注了一个公众号(订阅),有新文章时自动推送给你(通知)
  • 天气预报:气象站(被观察者)数据变化时,多个显示屏(观察者)自动更新
  • 报纸订阅:报社(被观察者)出版新报纸时,订阅者(观察者)自动收到
  • 股票行情:股价变化时,所有关注该股票的用户自动收到通知
  • DOM 事件:按钮点击时,所有注册的事件监听器自动被调用

四、示例1:股票价格观察者 ​

问题场景 ​

多个投资者关注同一只股票的价格变化,价格更新时自动通知所有投资者。

java
import java.util.ArrayList;
import java.util.List;
import java.util.HashMap;
import java.util.Map;

// ========== 观察者接口 ==========

/**
 * 股票观察者接口
 */
interface StockObserver {
    void update(String stockName, double price, double change);
    String getObserverName();
}

// ========== 主题接口 ==========

/**
 * 股票主题接口
 */
interface StockSubject {
    void addObserver(StockObserver observer);
    void removeObserver(StockObserver observer);
    void notifyObservers();
}

// ========== 具体主题 ==========

/**
 * 股票(具体主题)
 */
class Stock implements StockSubject {
    private final String name;
    private double price;
    private double previousPrice;
    private final List<StockObserver> observers = new ArrayList<>();

    public Stock(String name, double initialPrice) {
        this.name = name;
        this.price = initialPrice;
        this.previousPrice = initialPrice;
    }

    @Override
    public void addObserver(StockObserver observer) {
        observers.add(observer);
        System.out.println("  [股票 " + name + "] " + observer.getObserverName() + " 已订阅");
    }

    @Override
    public void removeObserver(StockObserver observer) {
        observers.remove(observer);
        System.out.println("  [股票 " + name + "] " + observer.getObserverName() + " 已取消订阅");
    }

    @Override
    public void notifyObservers() {
        double change = price - previousPrice;
        double changePercent = (change / previousPrice) * 100;
        for (StockObserver observer : observers) {
            observer.update(name, price, changePercent);
        }
    }

    /**
     * 股价变化时自动通知观察者
     */
    public void setPrice(double newPrice) {
        this.previousPrice = this.price;
        this.price = newPrice;
        System.out.println("\n[股票 " + name + "] 价格变化: "
                + previousPrice + " -> " + newPrice);
        notifyObservers();
    }

    public String getName() { return name; }
    public double getPrice() { return price; }
}

// ========== 具体观察者 ==========

/**
 * 个人投资者
 */
class IndividualInvestor implements StockObserver {
    private String name;
    private double budget;
    private Map<String, Double> holdings = new HashMap<>();

    public IndividualInvestor(String name, double budget) {
        this.name = name;
        this.budget = budget;
    }

    @Override
    public String getObserverName() { return name; }

    @Override
    public void update(String stockName, double price, double change) {
        System.out.println("  [投资者 " + name + "] 收到通知: "
                + stockName + " 当前价格: " + price
                + " 变化: " + String.format("%+.2f%%", change));

        // 简单的投资策略:涨了卖,跌了买
        if (change < -2.0 && budget >= price) {
            System.out.println("    -> " + name + " 决定买入 " + stockName);
            buy(stockName, price);
        } else if (change > 3.0 && holdings.containsKey(stockName)) {
            System.out.println("    -> " + name + " 决定卖出 " + stockName);
            sell(stockName, price);
        }
    }

    private void buy(String stockName, double price) {
        budget -= price;
        holdings.merge(stockName, price, Double::sum);
        System.out.println("    -> 买入成功,剩余预算: " + budget);
    }

    private void sell(String stockName, double price) {
        budget += price;
        holdings.remove(stockName);
        System.out.println("    -> 卖出成功,当前预算: " + budget);
    }
}

/**
 * 机构投资者(基金公司)
 */
class InstitutionalInvestor implements StockObserver {
    private String name;
    private double totalInvestment;

    public InstitutionalInvestor(String name) {
        this.name = name;
    }

    @Override
    public String getObserverName() { return name; }

    @Override
    public void update(String stockName, double price, double change) {
        System.out.println("  [机构 " + name + "] 收到通知: "
                + stockName + " 价格: " + price
                + " 变化: " + String.format("%+.2f%%", change));

        // 机构的投资策略更复杂
        if (Math.abs(change) > 5.0) {
            System.out.println("    -> [风控] " + name + " 触发大波动警报!变化: "
                    + String.format("%+.2f%%", change));
        }
    }
}

/**
 * 股价监控面板
 */
class StockDisplay implements StockObserver {
    private String name;

    public StockDisplay(String name) {
        this.name = name;
    }

    @Override
    public String getObserverName() { return name; }

    @Override
    public void update(String stockName, double price, double change) {
        String arrow = change > 0 ? "↑" : change < 0 ? "↓" : "→";
        System.out.println("  [面板 " + name + "] " + stockName
                + ": " + price + " " + arrow
                + " " + String.format("%+.2f%%", change));
    }
}

// ========== 客户端代码 ==========

public class StockObserverDemo {
    public static void main(String[] args) {
        // 创建股票(主题)
        Stock appleStock = new Stock("AAPL", 150.0);
        Stock googleStock = new Stock("GOOGL", 2800.0);

        // 创建观察者
        StockObserver alice = new IndividualInvestor("Alice", 10000);
        StockObserver bob = new IndividualInvestor("Bob", 5000);
        StockObserver fund = new InstitutionalInvestor("高盛基金");
        StockObserver display1 = new StockDisplay("大厅显示屏");
        StockObserver display2 = new StockDisplay("手机App");

        // 订阅
        System.out.println("===== 订阅股票 =====");
        appleStock.addObserver(alice);
        appleStock.addObserver(bob);
        appleStock.addObserver(fund);
        appleStock.addObserver(display1);
        googleStock.addObserver(display2);
        googleStock.addObserver(alice);

        // 股价变化
        System.out.println("\n===== AAPL 价格变化 =====");
        appleStock.setPrice(155.0);

        System.out.println("\n===== GOOGL 大幅下跌 =====");
        googleStock.setPrice(2650.0);

        // 取消订阅
        System.out.println("\n===== 取消订阅 =====");
        appleStock.removeObserver(bob);

        System.out.println("\n===== AAPL 再次变化 =====");
        appleStock.setPrice(148.0);
    }
}

五、示例2:自定义事件总线(EventBus) ​

问题场景 ​

实现一个简单的事件总线,支持发布/订阅模式。

java
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

// ========== 事件基类 ==========

/**
 * 事件基类
 */
abstract class Event {
    private final long timestamp;
    private final String source;

    protected Event(String source) {
        this.timestamp = System.currentTimeMillis();
        this.source = source;
    }

    public long getTimestamp() { return timestamp; }
    public String getSource() { return source; }
}

// ========== 具体事件 ==========

/**
 * 用户注册事件
 */
class UserRegisteredEvent extends Event {
    private final String username;

    public UserRegisteredEvent(String source, String username) {
        super(source);
        this.username = username;
    }

    public String getUsername() { return username; }
}

/**
 * 订单创建事件
 */
class OrderCreatedEvent extends Event {
    private final String orderId;
    private final double amount;

    public OrderCreatedEvent(String source, String orderId, double amount) {
        super(source);
        this.orderId = orderId;
        this.amount = amount;
    }

    public String getOrderId() { return orderId; }
    public double getAmount() { return amount; }
}

/**
 * 支付完成事件
 */
class PaymentCompletedEvent extends Event {
    private final String orderId;
    private final double amount;
    private final String paymentMethod;

    public PaymentCompletedEvent(String source, String orderId,
                                  double amount, String paymentMethod) {
        super(source);
        this.orderId = orderId;
        this.amount = amount;
        this.paymentMethod = paymentMethod;
    }

    public String getOrderId() { return orderId; }
    public double getAmount() { return amount; }
    public String getPaymentMethod() { return paymentMethod; }
}

// ========== 事件监听器接口 ==========

/**
 * 事件监听器接口
 */
interface EventListener {
    void onEvent(Event event);
}

/**
 * 强类型事件监听器
 */
interface TypedEventListener<T extends Event> {
    void onEvent(T event);
}

// ========== 事件总线 ==========

/**
 * 简单的事件总线实现
 * 支持按事件类型订阅和发布
 */
class EventBus {
    // 事件类型 -> 监听器列表
    private final Map<Class<? extends Event>, List<EventListener>> listeners
            = new ConcurrentHashMap<>();
    // 强类型监听器
    private final Map<Class<? extends Event>, List<TypedEventListener<? extends Event>>> typedListeners
            = new ConcurrentHashMap<>();

    /**
     * 注册事件监听器
     */
    public <T extends Event> void subscribe(Class<T> eventType, TypedEventListener<T> listener) {
        typedListeners.computeIfAbsent(eventType, k -> new CopyOnWriteArrayList<>())
                .add(listener);
        System.out.println("[EventBus] 注册监听器: " + eventType.getSimpleName());
    }

    /**
     * 注册通用事件监听器
     */
    public void subscribeAll(EventListener listener) {
        // 这个监听器会接收所有事件
        // 在实际实现中需要特殊处理
        System.out.println("[EventBus] 注册全局监听器");
    }

    /**
     * 取消订阅
     */
    public <T extends Event> void unsubscribe(Class<T> eventType, TypedEventListener<T> listener) {
        List<TypedEventListener<? extends Event>> list = typedListeners.get(eventType);
        if (list != null) {
            list.remove(listener);
            System.out.println("[EventBus] 取消监听: " + eventType.getSimpleName());
        }
    }

    /**
     * 发布事件
     */
    @SuppressWarnings("unchecked")
    public <T extends Event> void publish(T event) {
        Class<?> eventType = event.getClass();
        System.out.println("[EventBus] 发布事件: " + eventType.getSimpleName()
                + " (来源: " + event.getSource() + ")");

        // 通知所有匹配类型的监听器
        for (Map.Entry<Class<? extends Event>, List<TypedEventListener<? extends Event>>> entry
                : typedListeners.entrySet()) {
            if (entry.getKey().isAssignableFrom(eventType)) {
                for (TypedEventListener listener : entry.getValue()) {
                    try {
                        listener.onEvent(event);
                    } catch (Exception e) {
                        System.err.println("[EventBus] 监听器执行异常: " + e.getMessage());
                    }
                }
            }
        }
    }
}

// ========== 具体监听器 ==========

/**
 * 日志监听器:记录所有事件
 */
class LoggingListener implements TypedEventListener<Event> {
    @Override
    public void onEvent(Event event) {
        System.out.println("  [日志] " + event.getClass().getSimpleName()
                + " at " + event.getTimestamp()
                + " from " + event.getSource());
    }
}

/**
 * 用户服务监听器
 */
class UserServiceListener implements TypedEventListener<UserRegisteredEvent> {
    @Override
    public void onEvent(UserRegisteredEvent event) {
        System.out.println("  [用户服务] 发送欢迎邮件给: " + event.getUsername());
        System.out.println("  [用户服务] 初始化用户积分账户: " + event.getUsername());
    }
}

/**
 * 营销服务监听器
 */
class MarketingListener implements TypedEventListener<UserRegisteredEvent> {
    @Override
    public void onEvent(UserRegisteredEvent event) {
        System.out.println("  [营销] 发送新用户优惠券给: " + event.getUsername());
    }
}

/**
 * 库存服务监听器
 */
class InventoryListener implements TypedEventListener<OrderCreatedEvent> {
    @Override
    public void onEvent(OrderCreatedEvent event) {
        System.out.println("  [库存] 扣减商品库存,订单: " + event.getOrderId());
    }
}

/**
 * 通知服务监听器
 */
class NotificationListener implements TypedEventListener<PaymentCompletedEvent> {
    @Override
    public void onEvent(PaymentCompletedEvent event) {
        System.out.println("  [通知] 发送支付成功通知,订单: " + event.getOrderId()
                + " 金额: " + event.getAmount());
    }
}

// ========== 客户端代码 ==========

public class EventBusDemo {
    public static void main(String[] args) {
        EventBus eventBus = new EventBus();

        // 注册监听器
        System.out.println("===== 注册监听器 =====");
        eventBus.subscribe(UserRegisteredEvent.class, new UserServiceListener());
        eventBus.subscribe(UserRegisteredEvent.class, new MarketingListener());
        eventBus.subscribe(OrderCreatedEvent.class, new InventoryListener());
        eventBus.subscribe(PaymentCompletedEvent.class, new NotificationListener());

        // 发布事件
        System.out.println("\n===== 发布用户注册事件 =====");
        eventBus.publish(new UserRegisteredEvent("UserService", "张三"));

        System.out.println("\n===== 发布订单创建事件 =====");
        eventBus.publish(new OrderCreatedEvent("OrderService", "ORD001", 299.0));

        System.out.println("\n===== 发布支付完成事件 =====");
        eventBus.publish(new PaymentCompletedEvent("PaymentService", "ORD001", 299.0, "支付宝"));

        // 取消订阅
        System.out.println("\n===== 取消订阅 =====");
        eventBus.unsubscribe(UserRegisteredEvent.class, new MarketingListener() {
            // 注意:实际使用中需要保存监听器引用
        });
    }
}

六、Java 内置观察者模式 ​

java
import java.util.Observable;
import java.util.Observer;

/**
 * Java 9+ 已废弃 java.util.Observable
 * 推荐使用自定义实现或 RxJava/Project Reactor
 *
 * 以下是 Java 8 的用法示例(仅供了解)
 */

// 被观察者(已废弃)
class StockPrice extends Observable {
    private String name;
    private double price;

    public StockPrice(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public void setPrice(double newPrice) {
        this.price = newPrice;
        setChanged();  // 标记状态已改变
        notifyObservers();  // 通知所有观察者
    }

    public String getName() { return name; }
    public double getPrice() { return price; }
}

// 观察者(已废弃)
class PriceDisplay implements Observer {
    private String name;

    public PriceDisplay(String name) {
        this.name = name;
    }

    @Override
    public void update(Observable o, Object arg) {
        StockPrice stock = (StockPrice) o;
        System.out.println("  [" + name + "] " + stock.getName()
                + " 价格更新: " + stock.getPrice());
    }
}

七、UML 类图 ​

┌──────────────────┐          ┌──────────────────┐
│    Subject        │─────────▶│    Observer       │
│  (被观察者/主题)    │ 通知     │   (观察者)         │
│  +attach()       │─────────▶│  +update()       │
│  +detach()       │          └──────────────────┘
│  +notify()       │                   ▲
└────────┬─────────┘                   │ 实现
         │ 实现                ┌────────┴────────┐
┌────────┴─────────┐          │ConcreteObserver 1│
│ConcreteSubject    │          │  +update()      │
│  -state           │          └─────────────────┘
│  +getState()      │          ┌─────────────────┐
│  +setState()      │          │ConcreteObserver 2│
└──────────────────┘          │  +update()      │
                              └─────────────────┘

八、优缺点分析 ​

优点 ​

  1. 松耦合:观察者和被观察者之间没有直接依赖
  2. 支持广播通信:一个通知可以发送给多个观察者
  3. 符合开闭原则:新增观察者不需要修改被观察者
  4. 动态订阅/取消:可以在运行时动态添加或移除观察者
  5. 实时通知:状态变化时立即通知所有观察者

缺点 ​

  1. 通知顺序不确定:观察者的执行顺序可能不确定
  2. 循环依赖:观察者和被观察者之间可能产生循环调用
  3. 性能问题:观察者过多时,通知所有观察者可能影响性能
  4. 内存泄漏:如果观察者没有正确取消订阅,可能导致内存泄漏
  5. 通知丢失:如果观察者在通知过程中抛出异常,可能影响其他观察者

九、使用场景 ​

  • GUI 事件处理:按钮点击、键盘输入等事件
  • 消息推送:微信公众号、新闻推送
  • 数据绑定:MVC 框架中的数据绑定
  • Spring 事件机制:ApplicationEvent 和 ApplicationListener
  • 消息队列:Kafka、RabbitMQ 等消息中间件
  • 股票行情:实时股价更新通知
  • 日志系统:多个日志处理器同时处理日志事件

十、Spring 事件机制示例 ​

java
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;

/**
 * Spring 事件机制就是观察者模式的实现
 */

// 1. 定义事件
class OrderCreatedEvent2 extends ApplicationEvent {
    private final String orderId;

    public OrderCreatedEvent2(Object source, String orderId) {
        super(source);
        this.orderId = orderId;
    }

    public String getOrderId() { return orderId; }
}

// 2. 发布事件
// @Service
// class OrderService {
//     @Autowired
//     private ApplicationEventPublisher publisher;
//
//     public void createOrder(String orderId) {
//         // ... 创建订单逻辑
//         publisher.publishEvent(new OrderCreatedEvent2(this, orderId));
//     }
// }

// 3. 监听事件
// @Component
// class OrderEventHandler {
//     @EventListener
//     public void handleOrderCreated(OrderCreatedEvent2 event) {
//         System.out.println("处理订单创建事件: " + event.getOrderId());
//     }
//
//     @EventListener(condition = "#event.amount > 100")
//     public void handleLargeOrder(OrderCreatedEvent2 event) {
//         System.out.println("大额订单: " + event.getOrderId());
//     }
// }

十一、最佳实践 ​

  1. 使用弱引用:防止内存泄漏(Java 的 WeakReference)
  2. 异步通知:对于耗时操作,使用异步方式通知观察者
  3. 异常处理:在通知观察者时捕获异常,避免一个观察者影响其他观察者
  4. 事件过滤:让观察者可以只订阅感兴趣的事件类型
  5. 使用线程安全的集合:在多线程环境下使用 CopyOnWriteArrayList 等线程安全集合
java
// 推荐:使用 CopyOnWriteArrayList 保证线程安全
private final List<Observer> observers = new CopyOnWriteArrayList<>();

// 推荐:异常隔离
public void notifyObservers() {
    for (Observer observer : observers) {
        try {
            observer.update(this);
        } catch (Exception e) {
            log.error("通知观察者失败", e);
        }
    }
}

总结:观察者模式实现了对象之间的一对多依赖关系,当一个对象状态变化时,所有依赖于它的对象都会得到通知。它是 Java 中最常用的设计模式之一,在 GUI 编程、消息推送、Spring 事件机制等场景中广泛应用。关键在于正确管理观察者的订阅和取消订阅,避免内存泄漏。