设计模式-观察者模式


设计模式 - 观察者模式

从一个实际场景说起

假设你在开发一个消息推送系统

  • 用户可以订阅不同的频道(如新闻频道、体育频道、科技频道)
  • 当频道有新消息时,需要推送给所有订阅了该频道的用户
  • 用户可以随时订阅或取消订阅

如果用传统方式实现,你可能会写出这样的代码:

public class NewsChannel {
    private List<User> users = new ArrayList<>();
    
    public void addUser(User user) {
        users.add(user);
    }
    
    public void removeUser(User user) {
        users.remove(user);
    }
    
    public void publishNews(String news) {
        // 遍历所有用户,推送消息
        for (User user : users) {
            user.receive(news);
        }
    }
}

这看起来没问题,但如果需求变了:

  • 新增用户类型:VIP用户、普通用户、企业用户
  • 不同用户接收消息的方式不同:邮件、短信、APP推送
  • 需要支持其他订阅对象,如日志系统、统计系统

你会发现代码越来越难维护,因为发布消息的逻辑和通知用户的逻辑耦合在一起

观察者模式就是为了解决这种”一对多”的依赖关系而生的。

什么是观察者模式

定义

观察者模式定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知并被自动更新。

核心思想

就像报纸订阅

  • 报社是被观察者,负责出版报纸
  • 读者是观察者,订阅报纸
  • 当有新报纸时,报社自动送给所有订阅的读者

角色分工

Subject(被观察者/主题)
├─ 持有观察者列表
├─ 提供注册/注销观察者的方法
└─ 状态改变时通知所有观察者

Observer(观察者)
├─ 定义更新接口
└─ 接收到通知后执行具体操作

ConcreteSubject(具体主题)
└─ 实现Subject接口,存储具体状态

ConcreteObserver(具体观察者)
└─ 实现Observer接口,定义具体的更新逻辑

实战示例:消息推送系统

完整实现

1. 定义观察者接口

/**
 * 观察者接口
 */
public interface Observer {
    /**
     * 接收到消息时的更新方法
     * @param message 消息内容
     */
    void update(String message);
}

2. 定义被观察者接口

/**
 * 被观察者接口(主题)
 */
public interface Subject {
    /**
     * 注册观察者
     */
    void registerObserver(Observer observer);
    
    /**
     * 移除观察者
     */
    void removeObserver(Observer observer);
    
    /**
     * 通知所有观察者
     */
    void notifyObservers(String message);
}

3. 实现具体主题:消息频道

import java.util.ArrayList;
import java.util.List;

/**
 * 消息频道(具体主题)
 */
public class MessageChannel implements Subject {
    
    private String channelName;
    private List<Observer> observers = new ArrayList<>();
    
    public MessageChannel(String channelName) {
        this.channelName = channelName;
    }
    
    @Override
    public void registerObserver(Observer observer) {
        if (!observers.contains(observer)) {
            observers.add(observer);
            System.out.println("[" + observer + "] 订阅了频道: " + channelName);
        }
    }
    
    @Override
    public void removeObserver(Observer observer) {
        if (observers.remove(observer)) {
            System.out.println("[" + observer + "] 取消订阅频道: " + channelName);
        }
    }
    
    @Override
    public void notifyObservers(String message) {
        System.out.println("\n===== 频道【" + channelName + "】发布新消息 =====");
        System.out.println("消息内容: " + message);
        System.out.println("正在通知 " + observers.size() + " 个订阅者...\n");
        
        for (Observer observer : observers) {
            observer.update(message);
        }
    }
    
    /**
     * 发布消息(业务方法)
     */
    public void publishMessage(String message) {
        notifyObservers(message);
    }
    
    public String getChannelName() {
        return channelName;
    }
}

4. 实现具体观察者:用户

/**
 * 用户观察者(通过邮件接收)
 */
public class EmailUser implements Observer {
    
    private String username;
    private String email;
    
    public EmailUser(String username, String email) {
        this.username = username;
        this.email = email;
    }
    
    @Override
    public void update(String message) {
        System.out.println("【邮件通知】用户: " + username);
        System.out.println("  邮箱: " + email);
        System.out.println("  接收到消息: " + message);
        System.out.println("  已发送邮件到 " + email + "\n");
    }
    
    @Override
    public String toString() {
        return username;
    }
}

/**
 * 用户观察者(通过短信接收)
 */
public class SmsUser implements Observer {
    
    private String username;
    private String phone;
    
    public SmsUser(String username, String phone) {
        this.username = username;
        this.phone = phone;
    }
    
    @Override
    public void update(String message) {
        System.out.println("【短信通知】用户: " + username);
        System.out.println("  手机: " + phone);
        System.out.println("  接收到消息: " + message);
        System.out.println("  已发送短信到 " + phone + "\n");
    }
    
    @Override
    public String toString() {
        return username;
    }
}

/**
 * 用户观察者(通过APP推送)
 */
public class AppUser implements Observer {
    
    private String username;
    private String deviceId;
    
    public AppUser(String username, String deviceId) {
        this.username = username;
        this.deviceId = deviceId;
    }
    
    @Override
    public void update(String message) {
        System.out.println("【APP推送】用户: " + username);
        System.out.println("  设备ID: " + deviceId);
        System.out.println("  接收到消息: " + message);
        System.out.println("  已推送到设备 " + deviceId + "\n");
    }
    
    @Override
    public String toString() {
        return username;
    }
}

5. 客户端使用

public class MessagePushDemo {
    public static void main(String[] args) {
        // 1. 创建消息频道(被观察者)
        MessageChannel newsChannel = new MessageChannel("新闻频道");
        
        // 2. 创建用户(观察者)
        Observer user1 = new EmailUser("张三", "zhangsan@example.com");
        Observer user2 = new SmsUser("李四", "13800138000");
        Observer user3 = new AppUser("王五", "DEVICE_12345");
        Observer user4 = new EmailUser("赵六", "zhaoliu@example.com");
        
        // 3. 用户订阅频道
        newsChannel.registerObserver(user1);
        newsChannel.registerObserver(user2);
        newsChannel.registerObserver(user3);
        newsChannel.registerObserver(user4);
        
        // 4. 频道发布消息,所有订阅者自动收到通知
        newsChannel.publishMessage("今日头条:我国成功发射新一代通信卫星!");
        
        // 5. 用户取消订阅
        newsChannel.removeObserver(user2);
        
        // 6. 再次发布消息
        newsChannel.publishMessage("突发新闻:国际油价大幅上涨!");
        
        // 7. 新用户订阅
        Observer user5 = new SmsUser("孙七", "13900139000");
        newsChannel.registerObserver(user5);
        
        // 8. 发布消息
        newsChannel.publishMessage("体育快讯:中国队晋级世界杯决赛!");
    }
}

运行结果:

[张三] 订阅了频道: 新闻频道
[李四] 订阅了频道: 新闻频道
[王五] 订阅了频道: 新闻频道
[赵六] 订阅了频道: 新闻频道

===== 频道【新闻频道】发布新消息 =====
消息内容: 今日头条:我国成功发射新一代通信卫星!
正在通知 4 个订阅者...

【邮件通知】用户: 张三
  邮箱: zhangsan@example.com
  接收到消息: 今日头条:我国成功发射新一代通信卫星!
  已发送邮件到 zhangsan@example.com

【短信通知】用户: 李四
  手机: 13800138000
  接收到消息: 今日头条:我国成功发射新一代通信卫星!
  已发送短信到 13800138000

【APP推送】用户: 王五
  设备ID: DEVICE_12345
  接收到消息: 今日头条:我国成功发射新一代通信卫星!
  已推送到设备 DEVICE_12345

【邮件通知】用户: 赵六
  邮箱: zhaoliu@example.com
  接收到消息: 今日头条:我国成功发射新一代通信卫星!
  已发送邮件到 zhaoliu@example.com

[李四] 取消订阅频道: 新闻频道

===== 频道【新闻频道】发布新消息 =====
消息内容: 突发新闻:国际油价大幅上涨!
正在通知 3 个订阅者...

【邮件通知】用户: 张三
  邮箱: zhangsan@example.com
  接收到消息: 突发新闻:国际油价大幅上涨!
  已发送邮件到 zhangsan@example.com

【APP推送】用户: 王五
  设备ID: DEVICE_12345
  接收到消息: 突发新闻:国际油价大幅上涨!
  已推送到设备 DEVICE_12345

【邮件通知】用户: 赵六
  邮箱: zhaoliu@example.com
  接收到消息: 突发新闻:国际油价大幅上涨!
  已发送邮件到 zhaoliu@example.com

[孙七] 订阅了频道: 新闻频道

===== 频道【新闻频道】发布新消息 =====
消息内容: 体育快讯:中国队晋级世界杯决赛!
正在通知 4 个订阅者...

【邮件通知】用户: 张三
  邮箱: zhangsan@example.com
  接收到消息: 体育快讯:中国队晋级世界杯决赛!
  已发送邮件到 zhangsan@example.com

【APP推送】用户: 王五
  设备ID: DEVICE_12345
  接收到消息: 体育快讯:中国队晋级世界杯决赛!
  已推送到设备 DEVICE_12345

【邮件通知】用户: 赵六
  邮箱: zhaoliu@example.com
  接收到消息: 体育快讯:中国队晋级世界杯决赛!
  已发送邮件到 zhaoliu@example.com

【短信通知】用户: 孙七
  手机: 13900139000
  接收到消息: 体育快讯:中国队晋级世界杯决赛!
  已发送短信到 13900139000

代码解析

工作流程:

  1. 订阅阶段:用户调用 registerObserver() 订阅频道,频道将用户加入观察者列表
  2. 发布阶段:频道调用 publishMessage() 发布消息,触发 notifyObservers()
  3. 通知阶段:遍历观察者列表,调用每个观察者的 update() 方法
  4. 更新阶段:每个观察者根据自己的方式处理消息(邮件/短信/APP推送)
  5. 退订阶段:用户调用 removeObserver() 取消订阅,从观察者列表移除

关键特点:

  • 松耦合:频道不知道具体用户是谁,只知道观察者接口
  • 可扩展:新增用户类型只需实现 Observer 接口,无需修改频道代码
  • 动态订阅:运行时可以随时订阅或取消订阅
  • 广播通信:一次消息发布,所有订阅者都能收到

进阶示例:多频道消息推送

在实际应用中,用户可能订阅多个频道,每个频道有不同的消息:

改进版实现

/**
 * 改进的观察者接口,包含频道信息
 */
public interface Observer {
    void update(String channelName, String message);
}

/**
 * 改进的消息频道
 */
public class MessageChannel implements Subject {
    
    private String channelName;
    private List<Observer> observers = new ArrayList<>();
    
    public MessageChannel(String channelName) {
        this.channelName = channelName;
    }
    
    @Override
    public void notifyObservers(String message) {
        System.out.println("\n===== 【" + channelName + "】发布新消息 =====");
        
        for (Observer observer : observers) {
            observer.update(channelName, message);
        }
    }
    
    // 其他方法...
}

/**
 * 用户可以订阅多个频道
 */
public class MultiChannelUser implements Observer {
    
    private String username;
    private Set<String> subscribedChannels = new HashSet<>();
    
    public MultiChannelUser(String username) {
        this.username = username;
    }
    
    @Override
    public void update(String channelName, String message) {
        subscribedChannels.add(channelName);
        System.out.println("用户【" + username + "】收到【" + channelName + "】消息: " + message);
    }
    
    public void showSubscriptions() {
        System.out.println("用户【" + username + "】订阅的频道: " + subscribedChannels);
    }
}

// 使用
public class MultiChannelDemo {
    public static void main(String[] args) {
        // 创建多个频道
        MessageChannel newsChannel = new MessageChannel("新闻频道");
        MessageChannel sportsChannel = new MessageChannel("体育频道");
        MessageChannel techChannel = new MessageChannel("科技频道");
        
        // 创建用户
        MultiChannelUser user = new MultiChannelUser("张三");
        
        // 用户订阅多个频道
        newsChannel.registerObserver(user);
        sportsChannel.registerObserver(user);
        techChannel.registerObserver(user);
        
        // 各频道发布消息
        newsChannel.publishMessage("今日头条:我国成功发射卫星");
        sportsChannel.publishMessage("CBA总决赛:广东队夺冠");
        techChannel.publishMessage("苹果发布新款iPhone");
        
        // 查看用户订阅的所有频道
        user.showSubscriptions();
    }
}

输出:

===== 【新闻频道】发布新消息 =====
用户【张三】收到【新闻频道】消息: 今日头条:我国成功发射卫星

===== 【体育频道】发布新消息 =====
用户【张三】收到【体育频道】消息: CBA总决赛:广东队夺冠

===== 【科技频道】发布新消息 =====
用户【张三】收到【科技频道】消息: 苹果发布新款iPhone

用户【张三】订阅的频道: [新闻频道, 体育频道, 科技频道]

使用 Java 内置的观察者模式

Java 标准库提供了 java.util.Observablejava.util.Observer 类(已废弃,推荐自己实现),可以简化代码:

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

/**
 * 使用Java内置Observable(已废弃,仅作示例)
 */
public class NewsChannel extends Observable {
    
    private String channelName;
    
    public NewsChannel(String channelName) {
        this.channelName = channelName;
    }
    
    public void publishMessage(String message) {
        System.out.println("\n【" + channelName + "】发布消息: " + message);
        
        // 标记状态已改变
        setChanged();
        // 通知所有观察者
        notifyObservers(message);
    }
}

/**
 * 使用Java内置Observer
 */
public class User implements Observer {
    
    private String username;
    
    public User(String username) {
        this.username = username;
    }
    
    @Override
    public void update(Observable o, Object arg) {
        String message = (String) arg;
        System.out.println("用户【" + username + "】收到消息: " + message);
    }
}

// 使用
public class JavaObserverDemo {
    public static void main(String[] args) {
        NewsChannel channel = new NewsChannel("新闻频道");
        
        User user1 = new User("张三");
        User user2 = new User("李四");
        
        channel.addObserver(user1);
        channel.addObserver(user2);
        
        channel.publishMessage("今日头条:科技重大突破!");
    }
}

注意: ObservableObserver 在 Java 9 中已被标记为废弃,推荐自己实现观察者模式或使用事件框架。

Spring 中的事件机制

Spring 框架提供了完善的事件机制,本质就是观察者模式:

Spring 事件实现

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

/**
 * 自定义事件
 */
public class MessageEvent extends ApplicationEvent {
    
    private String channelName;
    private String message;
    
    public MessageEvent(Object source, String channelName, String message) {
        super(source);
        this.channelName = channelName;
        this.message = message;
    }
    
    public String getChannelName() { return channelName; }
    public String getMessage() { return message; }
}

/**
 * 事件发布者
 */
@Component
public class MessagePublisher {
    
    @Autowired
    private ApplicationEventPublisher eventPublisher;
    
    public void publishMessage(String channelName, String message) {
        MessageEvent event = new MessageEvent(this, channelName, message);
        eventPublisher.publishEvent(event);
        System.out.println("消息已发布: [" + channelName + "] " + message);
    }
}

/**
 * 事件监听器(观察者)
 */
@Component
public class EmailListener {
    
    @EventListener
    public void handleMessage(MessageEvent event) {
        System.out.println("【邮件监听器】收到消息: " + event.getMessage());
    }
}

@Component
public class SmsListener {
    
    @EventListener
    public void handleMessage(MessageEvent event) {
        System.out.println("【短信监听器】收到消息: " + event.getMessage());
    }
}

// 使用
@SpringBootApplication
public class SpringEventDemo implements CommandLineRunner {
    
    @Autowired
    private MessagePublisher publisher;
    
    public static void main(String[] args) {
        SpringApplication.run(SpringEventDemo.class, args);
    }
    
    @Override
    public void run(String... args) {
        publisher.publishMessage("新闻频道", "今日头条:重大科技突破!");
    }
}

什么时候使用观察者模式

✅ 适用场景

  1. 事件处理系统

    • 用户注册成功后,发送邮件、短信、记录日志
    • 订单状态变更后,通知库存、支付、物流系统
  2. 消息推送系统

    • 新闻订阅、股票价格变动通知
    • 社交媒体的好友动态推送
  3. GUI 事件处理

    • 按钮点击事件
    • 文本框内容变化监听
  4. 分布式系统

    • 配置中心配置变更通知
    • 服务注册与发现

关键判断标准

当你遇到以下情况时,考虑使用观察者模式:

  • 一对多关系:一个对象的改变需要通知多个对象
  • 松耦合需求:对象之间需要通信,但不能紧密耦合
  • 动态订阅:运行时需要动态添加或删除观察者
  • 事件驱动:系统采用事件驱动架构

观察者模式的优缺点

优点

  1. 解耦:被观察者和观察者之间是抽象耦合,符合依赖倒置原则
  2. 扩展性好:新增观察者无需修改被观察者代码,符合开闭原则
  3. 灵活:运行时可以动态添加、删除观察者
  4. 广播通信:一次触发,多个观察者响应

缺点

  1. 性能问题:观察者太多时,通知所有观察者会耗时较长
  2. 循环依赖:如果观察者和被观察者相互引用,可能导致循环调用
  3. 不知道具体变化:观察者只知道被观察者发生了变化,但不知道具体是什么变化
  4. 调试困难:采用异步方式时,调试和定位问题比较困难

最佳实践

1. 使用接口隔离观察者

// 好的做法:定义清晰的接口
public interface MessageObserver {
    void onMessage(String message);
}

// 不好的做法:直接依赖具体类
public class User {
    public void update(String message) { ... }
}

2. 处理异常,避免影响其他观察者

@Override
public void notifyObservers(String message) {
    for (Observer observer : observers) {
        try {
            observer.update(message);
        } catch (Exception e) {
            System.err.println("通知观察者失败: " + e.getMessage());
            // 继续通知其他观察者,不要因为一个失败而中断
        }
    }
}

3. 考虑异步通知

@Override
public void notifyObservers(String message) {
    observers.parallelStream().forEach(observer -> {
        observer.update(message);
    });
}

// 或使用线程池
private ExecutorService executor = Executors.newFixedThreadPool(10);

@Override
public void notifyObservers(String message) {
    for (Observer observer : observers) {
        executor.submit(() -> observer.update(message));
    }
}

4. 提供取消所有订阅的方法

public void clearObservers() {
    observers.clear();
}

总结

核心要点

  1. 定义:定义对象间一对多的依赖关系,当一个对象状态改变时,所有依赖它的对象都会收到通知

  2. 角色

    • Subject(被观察者):维护观察者列表,提供注册、注销、通知方法
    • Observer(观察者):定义更新接口
    • ConcreteSubject(具体主题):存储状态,状态改变时通知观察者
    • ConcreteObserver(具体观察者):实现具体的更新逻辑
  3. 工作流程

    观察者注册 → 被观察者状态改变 → 通知所有观察者 → 观察者执行更新
  4. 核心价值

    • 解耦:被观察者和观察者之间松耦合
    • 扩展:新增观察者不影响被观察者
    • 灵活:运行时动态管理观察者

一句话总结

观察者模式就是让被观察者维护一个观察者列表,当状态改变时自动通知所有观察者,实现对象间的松耦合通信。

就像订阅报纸一样,有新报纸时,报社自动送给所有订阅的读者,而不需要读者每天打电话询问。


作者:leejie
日期:2026年
标签:Java、设计模式、观察者模式、架构设计


文章作者: Leejie
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Leejie !
评论
  目录