青云
青云
发布于 2024-04-26 / 29 阅读
0
0

观察者模式和发布-订阅模式

观察者模式(Observer Pattern)和发布-订阅模式(Publish-Subscribe Pattern)都是用于处理对象之间的一对多依赖关系,但它们在实现方式和应用场景上有一些异同。

一、观察者模式:

观察者模式定义了一种一对多的依赖关系,使得当一个对象的状态发生变化时,其所有依赖对象都会得到通知并自动更新。在观察者模式中,有以下角色:

  • 主题(Subject): 也称为被观察者,负责维护一组观察者对象并通知它们状态的变化。

  • 观察者(Observer): 定义一个更新接口,用于接收主题通知的变化。

  • 具体主题(Concrete Subject): 实现主题接口,维护观察者列表并通知它们状态的变化。

  • 具体观察者(Concrete Observer): 实现观察者接口,具体的观察者对象,接收主题的通知并进行相应的更新。

二、发布-订阅模式:

发布-订阅模式也是一种一对多的依赖关系,但是它通过一个消息通道来实现,消息的发布者将消息发布到通道中,订阅者从通道中订阅消息。在发布-订阅模式中,有以下角色:

  • 消息通道(Message Channel): 用于发布者发布消息和订阅者订阅消息的中介。

  • 发布者(Publisher): 负责发布消息到消息通道。

  • 订阅者(Subscriber): 订阅感兴趣的消息类型,并从消息通道中接收相应的消息。

三、异同点:

  • 实现方式: 观察者模式通常是面向对象的,主题和观察者之间直接交互。而发布-订阅模式使用中介(消息通道)来进行消息的发布和订阅,发布者和订阅者之间没有直接的耦合关系。

  • 通信方式: 观察者模式中主题主动通知观察者,而发布-订阅模式中发布者和订阅者之间通过消息通道进行通信。

  • 灵活性: 发布-订阅模式更具有灵活性,可以支持多对多的关系,而观察者模式通常是一对多的关系。

使用情况:

  • 观察者模式: 当一个对象的状态变化需要通知多个依赖对象,并且对象之间有一定的关联时,可以使用观察者模式。例如,GUI界面组件的事件处理、数据模型和视图之间的同步等情况。

  • 发布-订阅模式: 当存在一个复杂的消息通信网络,多个发布者和多个订阅者之间需要进行灵活的消息交互时,可以使用发布-订阅模式。例如,分布式系统中的事件通知、消息队列等情况。

总之,观察者模式和发布-订阅模式都用于处理对象之间的一对多依赖关系,但它们在通信方式、实现方式和适用场景上有所不同。选择合适的模式取决于系统的需求和结构。

四、实战案例:天气监控系统

场景:气象站(被观察者)收集到温度 / 湿度变化后,自动通知手机 APP、网页端、大屏显示器(三个观察者)更新天气数据。

步骤 1:实现具体主题(气象站)

继承Observable,维护天气状态,提供更新天气的方法,状态变化时标记并通知观察者。

import java.util.Observable;

// 具体主题:气象站(被观察者)
public class WeatherStation extends Observable {
    // 被观察者的状态:温度、湿度
    private float temperature;
    private float humidity;

    // 更新天气数据(外部调用,触发状态变化)
    public void updateWeather(float temperature, float humidity) {
        this.temperature = temperature;
        this.humidity = humidity;
        // 1. 标记状态已变化(JDK强制要求,否则notify不生效)
        setChanged();
        // 2. 通知所有观察者,可传递参数(这里传递自身,观察者可直接获取状态)
        notifyObservers(this);
        // 无参通知:notifyObservers();
    }

    // 提供getter,让观察者获取状态(封装性)
    public float getTemperature() {
        return temperature;
    }

    public float getHumidity() {
        return humidity;
    }
}
步骤 2:实现多个具体观察者

实现Observer接口,重写update方法,接收通知并处理业务逻辑(如打印天气、更新界面)。

观察者 1:手机天气 APP

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

// 具体观察者1:手机天气APP
public class PhoneWeatherAPP implements Observer {
    // 观察者自身的标识(可选,根据业务定)
    private String appName;

    public PhoneWeatherAPP(String appName) {
        this.appName = appName;
    }

    // 核心:接收主题通知,执行更新逻辑
    @Override
    public void update(Observable o, Object arg) {
        // 校验参数并强转,获取被观察者的状态
        if (arg instanceof WeatherStation) {
            WeatherStation station = (WeatherStation) arg;
            System.out.println("【" + appName + "】收到天气更新:");
            System.out.println("温度:" + station.getTemperature() + "℃,湿度:" + station.getHumidity() + "%");
            System.out.println("------------------------");
        }
    }
}

观察者 2:网页端天气

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

// 具体观察者2:网页端天气
public class WebWeather implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        if (arg instanceof WeatherStation) {
            WeatherStation station = (WeatherStation) arg;
            System.out.println("【网页端天气】收到天气更新:");
            System.out.println("当前气温:" + station.getTemperature() + "℃,空气湿度:" + station.getHumidity() + "%");
            System.out.println("------------------------");
        }
    }
}
步骤 3:测试代码(主类)

创建被观察者和观察者,将观察者注册到被观察者,模拟天气状态变化,查看通知效果。

// 测试观察者模式
public class ObserverPatternTest {
    public static void main(String[] args) {
        // 1. 创建被观察者:气象站
        WeatherStation station = new WeatherStation();

        // 2. 创建观察者
        Observer phoneAPP = new PhoneWeatherAPP("华为天气");
        Observer webWeather = new WebWeather();

        // 3. 注册观察者(核心:建立一对多的依赖)
        station.addObserver(phoneAPP);
        station.addObserver(webWeather);

        // 4. 模拟天气变化,触发通知
        System.out.println("=== 第一次天气更新 ===");
        station.updateWeather(25.5f, 60.0f);

        System.out.println("=== 第二次天气更新 ===");
        station.updateWeather(28.0f, 55.0f);

        // 可选:移除某个观察者(如移除网页端)
        station.deleteObserver(webWeather);
        System.out.println("=== 第三次天气更新(已移除网页端) ===");
        station.updateWeather(22.0f, 70.0f);
    }
}

运行结果

=== 第一次天气更新 ===
【网页端天气】收到天气更新:
当前气温:25.5℃,空气湿度:60.0%
------------------------
【华为天气】收到天气更新:
温度:25.5℃,湿度:60.0%
------------------------
=== 第二次天气更新 ===
【网页端天气】收到天气更新:
当前气温:28.0℃,空气湿度:55.0%
------------------------
【华为天气】收到天气更新:
温度:28.0℃,湿度:55.0%
------------------------
=== 第三次天气更新(已移除网页端) ===
【华为天气】收到天气更新:
温度:22.0℃,湿度:70.0%
------------------------

五、自定义观察者模式(更灵活)

JDK 原生的Observable(不是接口),Java 是单继承,若具体主题需要继承其他类,就无法使用Observable,此时需要自定义抽象主题和抽象观察者(用接口实现,支持多实现)。

步骤 1:定义抽象主题(Subject)接口

封装注册、移除、通知的核心方法:

import java.util.List;

// 抽象主题接口(替代JDK的Observable类,支持多实现)
public interface Subject {
    // 注册观察者
    void registerObserver(Observer observer);
    // 移除观察者
    void removeObserver(Observer observer);
    // 通知所有观察者
    void notifyObservers();
}

步骤 2:定义抽象观察者(Observer)接口

封装更新方法:

// 抽象观察者接口(和JDK的Observer类似,可自定义参数)
public interface Observer {
    // 自定义更新方法,直接传递状态参数,更直观
    void update(float temperature, float humidity);
}

步骤 3:实现具体主题(气象站)

自己维护观察者集合(用 List),实现 Subject 接口的所有方法,状态变化时遍历集合通知观察者:

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

// 具体主题:气象站(实现Subject接口,可继承其他类)
public class WeatherStationCustom implements Subject {
    // 自己维护观察者集合
    private List<Observer> observerList;
    // 天气状态
    private float temperature;
    private float humidity;

    public WeatherStationCustom() {
        // 初始化观察者集合
        observerList = new ArrayList<>();
    }

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

    @Override
    public void removeObserver(Observer observer) {
        // 避免空指针,先判断是否存在
        if (observerList.contains(observer)) {
            observerList.remove(observer);
        }
    }

    @Override
    public void notifyObservers() {
        // 遍历所有观察者,调用update方法通知
        for (Observer observer : observerList) {
            observer.update(temperature, humidity);
        }
    }

    // 更新天气,触发通知
    public void updateWeather(float temperature, float humidity) {
        this.temperature = temperature;
        this.humidity = humidity;
        // 状态变化,通知观察者
        notifyObservers();
    }
}

步骤 4:实现具体观察者(和原生类似,实现自定义 Observer)

// 具体观察者:手机APP
public class PhoneWeatherAPPCustom implements Observer {
    private String appName;

    public PhoneWeatherAPPCustom(String appName) {
        this.appName = appName;
    }

    @Override
    public void update(float temperature, float humidity) {
        System.out.println("【" + appName + "】自定义模式-天气更新:");
        System.out.println("温度:" + temperature + "℃,湿度:" + humidity + "%");
        System.out.println("------------------------");
    }
}

// 具体观察者:网页端
public class WebWeatherCustom implements Observer {
    @Override
    public void update(float temperature, float humidity) {
        System.out.println("【网页端】自定义模式-天气更新:");
        System.out.println("气温:" + temperature + "℃,湿度:" + humidity + "%");
        System.out.println("------------------------");
    }
}

步骤 5:测试自定义模式

逻辑和原生一致,注册后触发状态变化即可:

public class CustomObserverPatternTest {
    public static void main(String[] args) {
        // 1. 创建被观察者
        Subject station = new WeatherStationCustom();
        // 2. 创建观察者
        Observer phoneAPP = new PhoneWeatherAPPCustom("小米天气");
        Observer web = new WebWeatherCustom();
        // 3. 注册
        station.registerObserver(phoneAPP);
        station.registerObserver(web);
        // 4. 触发更新
        System.out.println("=== 自定义模式-第一次更新 ===");
        ((WeatherStationCustom) station).updateWeather(26.0f, 65.0f);
    }
}

运行结果

=== 自定义模式-第一次更新 ===
【小米天气】自定义模式-天气更新:
温度:26.0℃,湿度:65.0%
------------------------
【网页端】自定义模式-天气更新:
气温:26.0℃,湿度:65.0%
------------------------


评论