《设计模式》第四篇:观察者模式

本期内容为自己总结归档,共分6章,本人遇到过的面试问题会重点标记。

《设计模式》第一篇:初识

《设计模式》第二篇:单例模式

《设计模式》第三篇:工厂模式

《设计模式》第四篇:观察模式

《设计模式》第五篇:策略模式

《设计模式》第六篇:装饰器模式

《设计模式》第七篇:适配器模式

《设计模式》第八篇:创建型模式

《设计模式》第九篇:结构型模式

《设计模式》第十篇:行为型模式

《设计模式》第十一篇:总结&常用案例

(若有任何疑问,可在评论区告诉我,看到就回复)

一、观察者模式的核心概念

1.1 观察者模式的定义

观察者模式(Observer Pattern)定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当主题对象状态发生变化时,会通知所有观察者对象,使它们能够自动更新。

1.2 观察者模式的结构

观察者模式包含四个关键角色:

  1. Subject(主题/被观察者):维护一个观察者列表,提供添加、删除观察者的方法,以及通知所有观察者的方法

  2. Observer(观察者):定义一个更新接口,用于在主题状态改变时接收通知

  3. ConcreteSubject(具体主题):具体被观察的对象,存储状态,状态改变时通知观察者

  4. ConcreteObserver(具体观察者):实现观察者接口,存储对具体主题的引用,实现更新逻辑

1.3 观察者模式 vs 发布-订阅模式

很多人容易混淆观察者模式和发布-订阅模式,它们确实相似但有所不同:

特性 观察者模式 发布-订阅模式
耦合度 松耦合 更松耦合
通信方式 直接调用观察者方法 通过消息队列/事件总线
关系 主题知道观察者 发布者不知道订阅者
同步性 通常是同步的 可以是异步的
实现复杂度 相对简单 更复杂

观察者模式可以看作是发布-订阅模式的简化版本。

1.4 设计要点

在设计观察者模式的程序时要注意以下几点:

  1. 要明确谁是观察者谁是被观察者,只要明白谁是关注对象,问题也就明白了。一般观察者与被观察者之间是多对一的关系,一个被观察对象可以有多个监听对象(观察者)。如一个编辑框,有鼠标点击的监听者,也有键盘的监听者,还有内容改变的监听者。

  2. Observable 在发送广播通知的时候,无须指定具体的 Observer,Observer 可以自己决定是否要订阅 Subject 的通知。

  3. 被观察者至少需要有三个方法:添加监听者、移除监听者、通知 Observer 的方法 ;观察者至少要有一个方法:更新方法,更新当前的内容,作出相应的处理。

  4. 添加监听者、移除监听者在不同的模型称谓中可能会有不同命名,如观察者模型中一般,addObserver,removeObserver;在源-监听器(Source/Listener)模型中一般是 attach/detach,应用在桌面编程的窗口中,还可能是 attachWindow/detachWindow,或 Register/UnRegister。不要被名称迷糊了,不管他们是什么名称,其实功能都是一样的,就是添加/删除观察者。

二、观察者模式的实现方式

2.1 基础实现demo:天气监测系统

天气监测系统:气象站收集天气数据,多个显示设备需要实时显示这些数据。

java 复制代码
// 1. 观察者接口
public interface Observer {
    void update(float temperature, float humidity, float pressure);
}

// 2. 主题接口
public interface Subject {
    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers();
}

// 3. 具体主题:气象数据
public 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 observer) {
        observers.add(observer);
    }
    
    @Override
    public void removeObserver(Observer observer) {
        int index = observers.indexOf(observer);
        if (index >= 0) {
            observers.remove(index);
        }
    }
    
    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(temperature, humidity, pressure);
        }
    }
    
    // 当气象数据更新时调用
    public void measurementsChanged() {
        notifyObservers();
    }
    
    // 设置气象数据(模拟从气象站获取数据)
    public void setMeasurements(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }
}

// 4. 具体观察者:当前状况显示
public class CurrentConditionsDisplay implements Observer {
    private float temperature;
    private float humidity;
    
    public CurrentConditionsDisplay(Subject weatherData) {
        weatherData.registerObserver(this);
    }
    
    @Override
    public void update(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        display();
    }
    
    public void display() {
        System.out.printf("当前状况: %.1f°C 和 %.1f%% 湿度%n", temperature, humidity);
    }
}

// 5. 具体观察者:气象统计显示
public class StatisticsDisplay implements Observer {
    private List<Float> temperatures = new ArrayList<>();
    
    public StatisticsDisplay(Subject weatherData) {
        weatherData.registerObserver(this);
    }
    
    @Override
    public void update(float temperature, float humidity, float pressure) {
        temperatures.add(temperature);
        display();
    }
    
    public void display() {
        float avg = temperatures.stream()
            .reduce(0f, Float::sum) / temperatures.size();
        float max = temperatures.stream().max(Float::compare).orElse(0f);
        float min = temperatures.stream().min(Float::compare).orElse(0f);
        
        System.out.printf("气象统计: 平均/最大/最小温度 = %.1f/%.1f/%.1f°C%n", avg, max, min);
    }
}

// 6. 测试代码
public class WeatherStation {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();
        
        CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
        StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
        
        // 模拟气象数据更新
        System.out.println("=== 第一次测量 ===");
        weatherData.setMeasurements(25.0f, 65.0f, 1013.1f);
        
        System.out.println("\n=== 第二次测量 ===");
        weatherData.setMeasurements(26.5f, 70.0f, 1012.5f);
        
        System.out.println("\n=== 第三次测量 ===");
        weatherData.setMeasurements(24.8f, 90.0f, 1014.0f);
    }
}

2.2 推模型 vs 拉模型

观察者模式有两种通知方式:

  1. 推模型(Push Model):主题将详细数据推送给观察者

  2. 拉模型(Pull Model):主题只通知观察者状态已改变,观察者自己拉取需要的数据

上面的示例使用的是推模型。下面是拉模型的实现:

java 复制代码
// 拉模型的观察者接口
public interface PullObserver {
    void update();
}

// 拉模型的主题接口
public interface PullSubject {
    void registerObserver(PullObserver observer);
    void removeObserver(PullObserver observer);
    void notifyObservers();
    
    // 提供获取数据的方法
    float getTemperature();
    float getHumidity();
    float getPressure();
}

// 具体实现
public class PullWeatherData implements PullSubject {
    private List<PullObserver> observers = new ArrayList<>();
    private float temperature;
    private float humidity;
    private float pressure;
    
    // 省略其他方法...
    
    @Override
    public void notifyObservers() {
        for (PullObserver observer : observers) {
            observer.update(); // 只通知,不传数据
        }
    }
    
    // 观察者通过getter方法获取数据
    @Override
    public float getTemperature() { return temperature; }
    @Override
    public float getHumidity() { return humidity; }
    @Override
    public float getPressure() { return pressure; }
}

// 拉模型的观察者实现
public class PullCurrentConditionsDisplay implements PullObserver {
    private PullSubject weatherData;
    
    public PullCurrentConditionsDisplay(PullSubject weatherData) {
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }
    
    @Override
    public void update() {
        // 主动拉取需要的数据
        float temperature = weatherData.getTemperature();
        float humidity = weatherData.getHumidity();
        System.out.printf("当前状况: %.1f°C 和 %.1f%% 湿度%n", temperature, humidity);
    }
}

三、观察者模式的高级应用

3.1 事件驱动架构

观察者模式是事件驱动架构的基础。让我们实现一个简单的事件系统demo:

java 复制代码
// 事件对象
public class WeatherEvent {
    private final float temperature;
    private final float humidity;
    private final float pressure;
    private final long timestamp;
    
    public WeatherEvent(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        this.timestamp = System.currentTimeMillis();
    }
    
    // getter方法...
}

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

// 事件管理器(简化版事件总线)
public class EventManager {
    private Map<String, List<EventListener>> listeners = new HashMap<>();
    
    public void subscribe(String eventType, EventListener listener) {
        listeners.computeIfAbsent(eventType, k -> new ArrayList<>()).add(listener);
    }
    
    public void unsubscribe(String eventType, EventListener listener) {
        List<EventListener> eventListeners = listeners.get(eventType);
        if (eventListeners != null) {
            eventListeners.remove(listener);
        }
    }
    
    public void publish(String eventType, WeatherEvent event) {
        List<EventListener> eventListeners = listeners.get(eventType);
        if (eventListeners != null) {
            for (EventListener listener : eventListeners) {
                listener.onEvent(event);
            }
        }
    }
}

// 使用示例
public class EventDrivenWeatherSystem {
    public static void main(String[] args) {
        EventManager eventManager = new EventManager();
        
        // 注册监听器
        eventManager.subscribe("weather.update", event -> {
            System.out.println("记录到日志: " + event);
        });
        
        eventManager.subscribe("weather.update", event -> {
            System.out.println("发送到仪表板: " + event.getTemperature() + "°C");
        });
        
        // 发布事件
        WeatherEvent event = new WeatherEvent(25.5f, 70.0f, 1013.0f);
        eventManager.publish("weather.update", event);
    }
}

3.2 异步观察者模式

在实际应用中,观察者的处理可能是耗时的,这时需要异步通知:

java 复制代码
public class AsyncWeatherData extends WeatherData {
    private ExecutorService executor = Executors.newFixedThreadPool(3);
    
    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            executor.submit(() -> {
                try {
                    observer.update(temperature, humidity, pressure);
                } catch (Exception e) {
                    // 处理异常,避免影响其他观察者
                    System.err.println("观察者处理失败: " + e.getMessage());
                }
            });
        }
    }
}

四、观察者模式在Spring框架中的应用

4.1 Spring的事件机制

Spring框架内置了强大的事件机制

java 复制代码
// 1. 自定义事件
public class WeatherEvent extends ApplicationEvent {
    private final float temperature;
    private final float humidity;
    
    public WeatherEvent(Object source, float temperature, float humidity) {
        super(source);
        this.temperature = temperature;
        this.humidity = humidity;
    }
    
    public float getTemperature() { return temperature; }
    public float getHumidity() { return humidity; }
}

// 2. 事件发布者
@Service
public class WeatherService {
    private final ApplicationEventPublisher eventPublisher;
    
    @Autowired
    public WeatherService(ApplicationEventPublisher eventPublisher) {
        this.eventPublisher = eventPublisher;
    }
    
    public void updateWeather(float temperature, float humidity) {
        // 业务逻辑...
        
        // 发布事件
        WeatherEvent event = new WeatherEvent(this, temperature, humidity);
        eventPublisher.publishEvent(event);
    }
}

// 3. 事件监听器(方式一:实现ApplicationListener接口)
@Component
public class WeatherEventListener implements ApplicationListener<WeatherEvent> {
    @Override
    public void onApplicationEvent(WeatherEvent event) {
        System.out.println("收到天气事件: " + event.getTemperature() + "°C");
    }
}

// 4. 事件监听器(方式二:使用@EventListener注解)
@Component
public class WeatherAnnotationListener {
    
    @EventListener
    public void handleWeatherEvent(WeatherEvent event) {
        System.out.println("注解方式处理天气事件: " + event.getHumidity() + "%");
    }
    
    // 支持条件表达式
    @EventListener(condition = "#event.temperature > 30")
    public void handleHotWeather(WeatherEvent event) {
        System.out.println("高温警告: " + event.getTemperature() + "°C");
    }
}

4.2 Spring事件机制的特点

Spring的事件机制比基本观察者模式更强大:

  1. 类型安全:基于泛型的事件类型

  2. 支持同步和异步 :通过@Async注解支持异步处理

  3. 事务绑定:支持事务事件监听

  4. 条件过滤:支持SpEL表达式的条件监听

  5. 事件继承:监听父类事件会收到子类事件

java 复制代码
// 异步事件监听
@Component
public class AsyncWeatherListener {
    
    @Async
    @EventListener
    public void handleAsync(WeatherEvent event) {
        // 异步处理,不会阻塞主线程
        System.out.println("异步处理天气事件");
    }
}

// 事务相关事件
@Component
public class TransactionalWeatherListener {
    
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void handleAfterCommit(WeatherEvent event) {
        // 事务提交后执行
        System.out.println("事务提交后处理天气事件");
    }
}

总结

5.1 何时使用观察者模式

观察者模式适用于以下场景:

  1. 当一个对象的状态改变需要通知其他对象,且不知道有多少对象需要通知时

  2. 当一个抽象模型有两个方面,其中一个方面依赖于另一个方面时

  3. 需要在运行时动态添加或删除监听器时

  4. 需要实现事件处理系统时

**5.2、**核心要点

  1. 设计原则:观察者模式体现了开闭原则(对扩展开放,对修改关闭)和依赖倒置原则(依赖于抽象而非具体实现)

  2. 实现方式

    • 基础实现:主题 + 观察者接口

    • 推模型 vs 拉模型:根据需求选择合适的数据传递方式

    • 异步通知:处理耗时的观察者逻辑

  3. 在Spring中的应用

    • Spring事件机制是观察者模式的实现

    • 支持同步/异步、条件过滤、事务绑定等高级特性

相关推荐
茶本无香2 小时前
设计模式之十一—桥接模式:解耦抽象与实现的艺术
设计模式·桥接模式
手握风云-2 小时前
JavaEE 进阶第十五期:Spring 日志的笔墨艺术
java·spring·java-ee
仟濹2 小时前
【Java加强】2 泛型 | 打卡day1
java·开发语言
Hx_Ma162 小时前
SpringBoot注册格式化器
java·spring boot·后端
V胡桃夹子2 小时前
VS Code / Lingma AI IDE Java 开发攻略手册
java·ide·人工智能
乔江seven2 小时前
【python轻量级Web框架 Flask 】1 Flask 初识
开发语言·后端·python·flask
独自破碎E2 小时前
【回溯】二叉树的所有路径
android·java
风景的人生2 小时前
application/x-www-form-urlencoded
java·mvc
sheji34162 小时前
【开题答辩全过程】以 基于Java的流浪猫救济中心系统的设计与实现为例,包含答辩的问题和答案
java·开发语言