Java案例中的观察者模式怎么用?

wen java案例 6

Java案例中的观察者模式怎么用?从理论到实战的完整解析

📖 目录导读

  1. 观察者模式核心概念 – 什么是观察者模式?它的设计意图与适用场景。
  2. Java内置实现 vs 自定义实现 – 两种方式的优缺点对比。
  3. 实战案例:天气预报系统 – 一步步搭建可运行的观察者模式代码。
  4. 常见问题与问答 – 开发者最关心的5个观察者模式问题。
  5. 性能与SEO优化建议 – 如何让代码更高效,文章更易被搜索引擎收录。

观察者模式核心概念

观察者模式(Observer Pattern)是Java中行为型设计模式之一,定义了对象之间的一对多依赖关系,当“主题”(Subject)对象的状态发生变化时,所有依赖于它的“观察者”(Observer)对象都会自动收到通知并更新。

Java案例中的观察者模式怎么用?

设计意图:实现松耦合的通信机制,让主题和观察者可以独立扩展。

适用场景

  • 当一个对象的改变需要同时改变其他对象,且不知道具体有多少对象需要改变时。
  • 事件驱动架构(如GUI按钮点击、消息队列)。
  • 实时数据推送(如股票行情、物联网传感器数据)。

关键角色

  • Subject(主题):维护观察者列表,提供注册、移除和通知方法。
  • Observer(观察者):定义一个更新接口,接收主题的通知。
  • ConcreteSubject(具体主题):存储感兴趣的状态,状态变化时通知观察者。
  • ConcreteObserver(具体观察者):实现更新接口,根据主题的状态做出响应。

Java内置实现 vs 自定义实现

1 Java内置观察者模式(JDK 9之前)

Java在java.util包中提供了Observable类和Observer接口,但自JDK 9起,这两个类已被标注为弃用,原因是它们的设计存在缺陷(如Observable是一个类而非接口,限制了灵活性;且无法实现事件传播的细粒度控制)。

2 自定义观察者模式(推荐方式)

现代Java开发中,更推荐手动编写观察者模式接口和实现,这样可以:

  • 使用接口而非抽象类,支持多继承(通过接口)。
  • 灵活控制通知的同步/异步机制。
  • 避免遗留API的线程安全问题。

代码结构示例

// 主题接口
interface Subject {
    void registerObserver(Observer o);
    void removeObserver(Observer o);
    void notifyObservers();
}
// 观察者接口
interface Observer {
    void update(float temperature, float humidity, float pressure);
}

实战案例:天气预报系统

假设我们要开发一个气象站系统,当天气数据(温度、湿度、气压)更新时,需要自动刷新多个显示面板(当前状态显示、统计显示、预报显示)。

步骤1:定义主题接口和具体主题

import java.util.ArrayList;
import java.util.List;
class WeatherData implements Subject {
    private List<Observer> observers;
    private float temperature;
    private float humidity;
    private float pressure;
    public WeatherData() {
        observers = new ArrayList<>();
    }
    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }
    @Override
    public void removeObserver(Observer o) {
        observers.remove(o);
    }
    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(temperature, humidity, pressure);
        }
    }
    // 当气象站更新数据时调用此方法
    public void setMeasurements(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }
    private void measurementsChanged() {
        notifyObservers();
    }
}

步骤2:定义观察者接口和具体观察者

// 当前状态显示面板
class CurrentConditionsDisplay implements Observer {
    private float temperature;
    private float humidity;
    @Override
    public void update(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        display();
    }
    public void display() {
        System.out.println("当前状态: " + temperature + "°C, 湿度 " + humidity + "%");
    }
}
// 统计显示面板(模拟)
class StatisticsDisplay implements Observer {
    private float maxTemp = 0.0f;
    private float minTemp = 200;
    private float tempSum = 0.0f;
    private int numReadings;
    @Override
    public void update(float temperature, float humidity, float pressure) {
        tempSum += temperature;
        numReadings++;
        if (temperature > maxTemp) maxTemp = temperature;
        if (temperature < minTemp) minTemp = temperature;
        display();
    }
    public void display() {
        System.out.println("温度统计: 平均 " + (tempSum / numReadings) + "°C, 最高 " + maxTemp + "°C, 最低 " + minTemp + "°C");
    }
}

步骤3:运行测试

public class WeatherStation {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();
        CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay();
        StatisticsDisplay statisticsDisplay = new StatisticsDisplay();
        weatherData.registerObserver(currentDisplay);
        weatherData.registerObserver(statisticsDisplay);
        System.out.println("--- 第一次天气更新 ---");
        weatherData.setMeasurements(25.5f, 65, 1013.1f);
        System.out.println("\n--- 第二次天气更新 ---");
        weatherData.setMeasurements(27.8f, 70, 1011.5f);
        // 移除一个观察者
        weatherData.removeObserver(currentDisplay);
        System.out.println("\n--- 移除当前显示面板后,第三次更新 ---");
        weatherData.setMeasurements(22.3f, 80, 1015.2f);
    }
}

输出结果

--- 第一次天气更新 ---
当前状态: 25.5°C, 湿度 65%
温度统计: 平均 25.5°C, 最高 25.5°C, 最低 25.5°C
--- 第二次天气更新 ---
当前状态: 27.8°C, 湿度 70%
温度统计: 平均 26.65°C, 最高 27.8°C, 最低 25.5°C
--- 移除当前显示面板后,第三次更新 ---
温度统计: 平均 25.23°C, 最高 27.8°C, 最低 22.3°C

注意:此案例展示了同步通知(notifyObservers()会阻塞直到所有观察者处理完毕),如果需异步处理,可使用ExecutorService包裹通知逻辑。


常见问题与问答

Q1:观察者模式与发布-订阅模式有什么区别?

A:在经典观察者模式中,主题直接调用观察者的方法(紧耦合的依赖),而发布-订阅模式中,增加了消息代理(Broker)作为中间层,发布者和订阅者完全解耦,Java中常通过事件总线(如EventBus库)实现发布-订阅。

Q2:如何避免观察者过多导致的性能问题?

A:可考虑:

  • 使用弱引用(WeakReference) 持有观察者,避免内存泄漏。
  • 对通知进行批处理节流(Throttle)。
  • 如果观察者数量巨大,改用反应式编程(如Spring WebFlux)压扁调用栈。

Q3:观察者模式是否支持多线程?

A:原生实现不支持线程安全,需要在registerObservernotifyObservers方法上加synchronized,或使用CopyOnWriteArrayList避免并发修改异常。

Q4:何时不应该使用观察者模式?

A

  • 当观察者数量极少且固定时,直接调用更简单。
  • 当主题状态变化非常频繁且观察者处理缓慢时,会导致性能瓶颈。
  • 如果观察者之间需要顺序依赖,则不适合(观察者不应彼此感知)。

Q5:观察者模式在Spring框架中的应用?

A:Spring使用ApplicationEvent@EventListener注解实现事件驱动,通过ApplicationEventPublisher发布事件,监听器自动接收处理,其本质就是观察者模式的变体。


性能与SEO优化建议

1 代码层面的性能优化

  • 移除不必要的观察者:确保不再使用的观察者及时注销,避免一直占用内存。
  • 懒加载观测者列表:如果观察者数量较少,使用ArrayList;如果经常增删,使用LinkedList
  • 批量通知:如果主题更新频繁,可合并多个连续状态变更为一次通知。

2 文章SEO优化技巧(适用本站)包含关键词**:本文标题“Java案例中的观察者模式怎么用?”包含完整查询意图。

  • H2/H3分级标签:符合搜索引擎层级解析,提升关键词相关性。
  • 代码块与实践案例:Google偏好包含可运行示例的文章(支持代码片段展示)。
  • 问答段落:提升用户在搜索结果中的点击率(Featured Snippet常见于问答格式)。
  • 内链与外链:本文未使用外链,如需可链接至其他设计模式文章,形成主题集群。

抱歉,评论功能暂时关闭!