观察者模式(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%
------------------------