【设计模式】【行为型模式】观察者模式(Observer)

👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD

🔥 2025本人正在沉淀中... 博客更新速度++

📫 欢迎+V: flzjcsg2,我们共同讨论Java深渊的奥秘

🎵 当你的天空突然下了大雨,那是我在为你炸乌云

文章目录

一、入门

什么是观察者模式?

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

为什么要观察者模式?

假设我们正在开发一个天气预报系统,其中:

  • WeatherStation(气象站):负责收集天气数据(如温度、湿度等)。
  • Display(显示设备):负责显示天气数据,比如手机App、电子屏等。

当气象站的数据更新时,所有显示设备都需要实时更新显示内容。

下面是没有观察者模式时的实现:

java 复制代码
class WeatherStation {
    private float temperature;
    private float humidity;
    private PhoneDisplay phoneDisplay;
    private TVDisplay tvDisplay;

    public void setPhoneDisplay(PhoneDisplay phoneDisplay) {
        this.phoneDisplay = phoneDisplay;
    }
    
    public void setTVDisplay(TVDisplay tvDisplay) {
        this.tvDisplay = tvDisplay;
    }

    public void removePhoneDisplay() {
        phoneDisplay = null;
    }

    public void removeTVDisplay() {
        phoneDisplay = null;
    }
    
    public void setMeasurements(float temperature, float humidity) {
        this.temperature = temperature;
        this.humidity = humidity;
        // 手动调用显示设备的更新方法
        if (phoneDisplay != null) {
            phoneDisplay.update(temperature, humidity);
        }
        if (tvDisplay != null) {
            tvDisplay.update(temperature, humidity);
        }
    }
}

紧耦合

如果没有观察者模式,气象站需要直接知道所有显示设备的存在,并手动调用它们的更新方法。例如:

问题:

  • 气象站和显示设备之间是紧耦合的,气象站需要知道所有显示设备的具体实现。
  • 如果新增一个显示设备(比如智能手表),需要修改气象站的代码,违反了开闭原则(对扩展开放,对修改关闭)。

难以动态管理依赖

如果显示设备需要动态添加或移除(比如用户关闭了某个显示设备),气象站需要手动管理这些设备的引用。

扩展性差

如果未来需要支持更多类型的观察者(比如日志记录器、报警系统等),气象站的代码会变得越来越臃肿,难以维护。

怎么实现观察者模式?

在观察者模式中有如下角色:

  • Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象。
  • ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
  • Observer:抽象观察者,是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
  • ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。

【案例】天气站 - 改

Observer观察者 : Observer接口

java 复制代码
interface Observer {
    void update(float temperature, float humidity);
}

Subject主题 : subject接口

java 复制代码
interface Subject {
    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers();
}

实现具体主题(气象站) : WeatherStation

java 复制代码
class WeatherStation implements Subject {
    private List<Observer> observers = new ArrayList<>();
    private float temperature;
    private float humidity;

    @Override
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }

    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(temperature, humidity);
        }
    }

    public void setMeasurements(float temperature, float humidity) {
        this.temperature = temperature;
        this.humidity = humidity;
        notifyObservers(); // 通知所有观察者
    }
}

实现具体观察者(显示设备) : PhoneDisplay类和TVDisplay

java 复制代码
class PhoneDisplay implements Observer {
    @Override
    public void update(float temperature, float humidity) {
        System.out.println("手机显示:温度 = " + temperature + ",湿度 = " + humidity);
    }
}

class TVDisplay implements Observer {
    @Override
    public void update(float temperature, float humidity) {
        System.out.println("电视显示:温度 = " + temperature + ",湿度 = " + humidity);
    }
}

测试类

java 复制代码
public class WeatherApp {
    public static void main(String[] args) {
        WeatherStation weatherStation = new WeatherStation();

        Observer phoneDisplay = new PhoneDisplay();
        Observer tvDisplay = new TVDisplay();

        weatherStation.registerObserver(phoneDisplay);
        weatherStation.registerObserver(tvDisplay);

        // 更新天气数据
        weatherStation.setMeasurements(25.5f, 60.0f);

        // 移除一个观察者
        weatherStation.removeObserver(tvDisplay);

        // 再次更新天气数据
        weatherStation.setMeasurements(26.0f, 58.0f);
    }
}

运行结果

shell 复制代码
手机显示:温度 = 25.5,湿度 = 60.0
电视显示:温度 = 25.5,湿度 = 60.0
手机显示:温度 = 26.0,湿度 = 58.0

二、观察者模式在源码中运用

Java 中的 java.util.Observer 和 java.util.Observable

Java 标准库中提供了观察者模式的实现,分别是 Observer 接口和 Observable 类。

  • Observable 是被观察者的基类,内部维护了一个观察者列表,并提供了 addObserver、deleteObserver 和 notifyObservers 方法。
  • Observer 是观察者接口,定义了 update 方法,用于接收通知。

Observer和Observable的使用

被观察者(具体主题) :WeatherData

java 复制代码
// 被观察者(主题)
class WeatherData extends Observable {
    private float temperature;
    private float humidity;

    public void setMeasurements(float temperature, float humidity) {
        this.temperature = temperature;
        this.humidity = humidity;
        setChanged(); // 标记状态已改变
        notifyObservers(); // 通知观察者
    }

    public float getTemperature() {
        return temperature;
    }

    public float getHumidity() {
        return humidity;
    }
}

观察者(具体观察者) : Display

java 复制代码
// 观察者
class Display implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        if (o instanceof WeatherData) {
            WeatherData weatherData = (WeatherData) o;
            float temperature = weatherData.getTemperature();
            float humidity = weatherData.getHumidity();
            System.out.println("当前温度: " + temperature + ",湿度: " + humidity);
        }
    }
}

测试

java 复制代码
public class ObserverPatternDemo {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();
        Display display = new Display();

        weatherData.addObserver(display); // 注册观察者

        weatherData.setMeasurements(25.5f, 60.0f); // 更新数据并通知观察者
    }
}

输出结果

shell 复制代码
当前温度: 25.5,湿度: 60.0

Observer和Observable的源码实现

观察者 :Observer类,入参Observable o:被观察的对象(主题)和 Object arg:传递给观察者的额外参数(可选)。

java 复制代码
public interface Observer {
    void update(Observable o, Object arg);
}

主题Observable类。

我们可以看到Vector<Observer>存储观察者列表。并且因为加了synchronized关键字,这些方法都是线程安全的。notifyObservers方法会遍历观察者列表,并调用每个观察者的update方法。

java 复制代码
public class Observable {
    // 标记对象是否已改变
    private boolean changed = false;

    // 观察者列表(使用 Vector 保证线程安全)
    private Vector<Observer> obs;

    public Observable() {
        obs = new Vector<>();
    }

    // 添加观察者
    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }

    // 删除观察者
    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }

    // 通知所有观察者(无参数)
    public void notifyObservers() {
        notifyObservers(null);
    }

    // 通知所有观察者(带参数)
    public void notifyObservers(Object arg) {
        Observer[] arrLocal;

        // 同步块,确保线程安全
        synchronized (this) {
            if (!changed) // 如果没有变化,直接返回
                return;
            arrLocal = obs.toArray(new Observer[obs.size()]);
            clearChanged(); // 重置变化标志
        }

        // 遍历观察者列表,调用 update 方法
        for (Observer observer : arrLocal) {
            observer.update(this, arg);
        }
    }

    // 删除所有观察者
    public synchronized void deleteObservers() {
        obs.removeAllElements();
    }

    // 标记对象已改变
    protected synchronized void setChanged() {
        changed = true;
    }

    // 重置变化标志
    protected synchronized void clearChanged() {
        changed = false;
    }

    // 检查对象是否已改变
    public synchronized boolean hasChanged() {
        return changed;
    }

    // 返回观察者数量
    public synchronized int countObservers() {
        return obs.size();
    }
}

Spring 框架中的事件机制

Spring 框架中的事件机制是基于观察者模式实现的。它允许开发者定义自定义事件,并通过监听器(观察者)来处理这些事件。

Spring事件机制的使用

自定义事件

java 复制代码
class CustomEvent extends ApplicationEvent {
    private String message;

    public CustomEvent(Object source, String message) {
        super(source);
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}

事件监听器(观察者)

java 复制代码
@Component
class CustomEventListener implements ApplicationListener<CustomEvent> {
    @Override
    public void onApplicationEvent(CustomEvent event) {
        System.out.println("收到事件: " + event.getMessage());
    }
}

事件发布者

java 复制代码
@Component
class CustomEventPublisher {
    private final AnnotationConfigApplicationContext context;

    public CustomEventPublisher(AnnotationConfigApplicationContext context) {
        this.context = context;
    }

    public void publishEvent(String message) {
        context.publishEvent(new CustomEvent(this, message));
    }
}

配置类

java 复制代码
@Configuration
@ComponentScan
class AppConfig {}

测试类

java 复制代码
public class SpringEventDemo {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        CustomEventPublisher publisher = context.getBean(CustomEventPublisher.class);
        publisher.publishEvent("Hello, Spring Event!");
        context.close();
    }
}

输出内容

shell 复制代码
收到事件: Hello, Spring Event!

Spring的事件机质的源码实现

Spring 事件机制的核心组件包括:

  • ApplicationEvent:事件的基类,所有自定义事件都需要继承它。对应观察者模式中的"事件"。
  • ApplicationListener:观察者接口,定义了处理事件的方法。对应观察者模式中的"观察者"。
  • ApplicationEventPublisher:事件发布者接口,用于发布事件。对应观察者模式中的"主题"。
  • ApplicationEventMulticaster:事件广播器,负责将事件分发给所有监听器。类似于观察者模式中的"通知机制"。

ApplicationEventApplicationEvent 是所有事件的基类,它继承自 java.util.EventObject

java 复制代码
public abstract class ApplicationEvent extends EventObject {
    private final long timestamp;            // timestamp: 事件发生的时间戳。

    public ApplicationEvent(Object source) { // source:事件源,通常是发布事件的对象。
        super(source);
        this.timestamp = System.currentTimeMillis();
    }

    public final long getTimestamp() {
        return this.timestamp;
    }
}

ApplicationListener接口 : ApplicationListener是观察者接口,定义了处理事件的方法。

java 复制代码
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
    void onApplicationEvent(E event);  // 当事件发生时,会调用此方法。
}

ApplicationEventPublisher接口: 是事件发布者接口,用于发布事件。

java 复制代码
@FunctionalInterface
public interface ApplicationEventPublisher {
    default void publishEvent(ApplicationEvent event) {
        publishEvent((Object) event);
    }

    void publishEvent(Object event);
}

ApplicationEventMulticaster接口:是事件广播器接口,负责将事件分发给所有监听器。

java 复制代码
public interface ApplicationEventMulticaster {
    void addApplicationListener(ApplicationListener<?> listener);
    void addApplicationListenerBean(String listenerBeanName);
    void removeApplicationListener(ApplicationListener<?> listener);
    void removeApplicationListenerBean(String listenerBeanName);
    void removeAllListeners();
    void multicastEvent(ApplicationEvent event); 
    void multicastEvent(ApplicationEvent event, ResolvableType eventType);
}

SimpleApplicationEventMulticasterSimpleApplicationEventMulticasterApplicationEventMulticaster的默认实现类。

java 复制代码
public class SimpleApplicationEventMulticaster extends AbstractApplicationEventMulticaster {
    // 遍历所有监听器,并调用 onApplicationEvent 方法。
    @Override
    public void multicastEvent(final ApplicationEvent event, ResolvableType eventType) {
        ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
        for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
            Executor executor = getTaskExecutor();
            if (executor != null) {
                executor.execute(() -> invokeListener(listener, event));
            } else {
                invokeListener(listener, event);
            }
        }
    }

    // 实际调用监听器的 onApplicationEvent 方法。
    protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
        try {
            listener.onApplicationEvent(event);
        } catch (ClassCastException ex) {
            // 处理类型转换异常
        }
    }
}

三、总结

观察者模式的优点

解耦 :主题(Subject)和观察者(Observer)之间是松耦合的(主题不需要知道观察者的具体实现,只需要知道观察者接口,观察者也不需要知道主题的具体实现,只需要实现观察者接口)

动态管理依赖:观察者可以动态注册和注销,而不需要修改主题的代码。支持运行时动态添加或移除观察者,灵活性高。
符合开闭原则 :可以轻松添加新的观察者,而不需要修改主题的代码。主题的代码不需要因为观察者的变化而修改。
广播通信 :主题可以一次性通知所有观察者,适合一对多的通信场景。观察者可以根据需要选择是否响应通知。
职责分离:主题负责维护状态和通知观察者。观察者负责处理状态变化的逻辑。职责分离使得代码更加清晰和可维护。

观察者模式的缺点

  • 性能问题
    • 如果观察者数量非常多,通知所有观察者可能会消耗大量时间。
    • 如果观察者的处理逻辑复杂,可能会导致性能瓶颈。
  • 内存泄漏
    • 如果观察者没有正确注销,可能会导致观察者无法被垃圾回收,从而引发内存泄漏。
    • 特别是在长时间运行的应用中,需要特别注意观察者的生命周期管理。
  • 调试困难
    • 由于观察者和主题是松耦合的,调试时可能难以追踪事件的传递路径。
    • 如果观察者的处理逻辑出现问题,可能不容易定位问题根源。
  • 事件顺序不确定
    • 观察者收到通知的顺序通常是不确定的,如果业务逻辑对顺序有要求,可能需要额外的处理。

观察者模式的适用场景

  • 事件驱动系统
    • 当一个对象的状态变化需要触发其他对象的操作时,可以使用观察者模式。
    • 例如:GUI 框架中的按钮点击事件、Spring 框架中的事件机制。
  • 一对多的依赖关系
    • 当一个对象的状态变化需要通知多个其他对象时,可以使用观察者模式。
    • 例如:气象站和多个显示设备的关系。
  • 跨系统的消息通知
    • 在分布式系统中,观察者模式可以用于实现消息的发布和订阅。
    • 例如:消息队列(MQ)中的发布-订阅模型。
  • 状态变化的广播
    • 当一个对象的状态变化需要广播给多个对象时,可以使用观察者模式。
    • 例如:游戏中的角色状态变化通知其他系统(如 UI、音效等)。
  • 解耦业务逻辑
    • 当需要将业务逻辑解耦为多个独立的模块时,可以使用观察者模式。
    • 例如:订单系统中的订单状态变化通知库存系统、物流系统等。

参考

黑马程序员Java设计模式详解, 23种Java设计模式(图解+框架源码分析+实战)_哔哩哔哩_bilibili

相关推荐
不会玩技术的技术girl1 小时前
使用Java爬虫获取京东商品评论API接口(JD.item_review)数据
java·开发语言·爬虫
计算机毕设指导62 小时前
基于Spring Boot的医院挂号就诊系统【免费送】
java·服务器·开发语言·spring boot·后端·spring·maven
Yolowuwu2 小时前
算法跟练第十一弹——二叉树
java·算法·leetcode
m0_748238922 小时前
Java面试题--设计模式
java·开发语言·设计模式
七灵微2 小时前
设计模式全解(含代码实例)
设计模式
青云交3 小时前
Java 大视界 -- 区块链赋能 Java 大数据:数据可信与价值流转(84)
java·大数据·区块链·智能合约·共识机制·数据可信·价值流转
汇匠源3 小时前
如何开发一个基于Java的商城小程序?
java·小程序
115432031q3 小时前
基于SpringBoot养老院平台系统功能实现十七
java·前端·后端
众智创新团队3 小时前
Android的Activity生命周期知识点总结,详情
android·java·开发语言
&岁月不待人&3 小时前
Android 常用设计模式和实例
java·开发语言·设计模式