观察者模式(Observer Pattern)详解

1. 什么是观察者模式?

观察者模式是一种行为型设计模式,它定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个主题对象。当主题对象状态发生变化时,它的所有依赖者(观察者)都会收到通知并自动更新。

观察者模式又被称为发布-订阅(Publish/Subscribe)模式、模型-视图(Model-View)模式或源-监听器(Source-Listener)模式。

2. 为什么需要观察者模式?

在以下情况下,观察者模式特别有用:

  1. 当一个对象的改变需要同时改变其他对象时:观察者模式可以实现这种一对多的通知机制
  2. 当一个对象必须通知其他对象,而它又不需要知道这些对象是谁时:主题只需要知道观察者实现了特定接口
  3. 当你需要维护对象之间的一致性时:不必使各个对象紧密耦合
  4. 当抽象模型有两个方面,其中一个方面依赖于另一个方面时:将这两者封装在独立的对象中允许您分别使用和修改它们

3. 观察者模式的核心概念

观察者模式主要涉及以下角色:

  1. 主题(Subject)

    • 知道它的观察者,可以有任意多个观察者观察同一个主题
    • 提供注册和删除观察者对象的接口
  2. 具体主题(Concrete Subject)

    • 存储观察者感兴趣的状态
    • 当状态发生变化时通知观察者
  3. 观察者(Observer)

    • 为那些在主题状态发生改变时需获得通知的对象定义一个更新接口
  4. 具体观察者(Concrete Observer)

    • 实现观察者更新接口以响应主题状态的变化
    • 保持与主题的一致性

4. 观察者模式的结构

观察者模式的UML类图如下:

+----------------+        +----------------+
| Subject        |<>----->| Observer       |
+----------------+        +----------------+
| attach()       |        | update()       |
| detach()       |        +----------------+
| notify()       |               ^
+----------------+               |
       ^                         |
       |                         |
+----------------+        +-------------------+
| ConcreteSubject|        | ConcreteObserver  |
+----------------+        +-------------------+
| getState()     |        | update()          |
| setState()     |        | observerState     |
+----------------+        +-------------------+

5. 观察者模式的基本实现

简单的气象站示例

下面是一个简单的气象站示例,展示了观察者模式的基本实现。

首先,定义观察者接口:

// 观察者接口
public interface Observer {
   
    void update(float temperature, float humidity, float pressure);
}

然后,定义主题接口:

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

接着,实现具体主题(气象数据):

// 具体主题 - 气象数据
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) {
   
        if (!observers.contains(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, pressure);
        }
    }
    
    // 当气象测量数据改变时,通知观察者
    public void measurementsChanged() {
   
        notifyObservers();
    }
    
    // 设置气象测量数据
    public void setMeasurements(float temperature, float humidity, float pressure) {
   
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }
}

然后,实现具体观察者(显示器):

// 具体观察者 - 当前状况显示
public class CurrentConditionsDisplay implements Observer {
   
    private float temperature;
    private float humidity;
    private Subject weatherData;
    
    public CurrentConditionsDisplay(Subject weatherData) {
   
        this.weatherData = 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.println("当前状况: 温度 " + temperature + "°C, 湿度 " + humidity + "%");
    }
}

// 具体观察者 - 统计信息显示
public class StatisticsDisplay implements Observer {
   
    private float maxTemp = 0.0f;
    private float minTemp = 200.0f;
    private float sumTemp = 0.0f;
    private int countReadings = 0;
    private Subject weatherData;
    
    public StatisticsDisplay(Subject weatherData) {
   
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }
    
    @Override
    public void update(float temperature, float humidity, float pressure) {
   
        sumTemp += temperature;
        countReadings++;
        
        if (temperature > maxTemp) {
   
            maxTemp = temperature;
        }
        
        if (temperature < minTemp) {
   
            minTemp = temperature;
        }
        
        display();
    }
    
    public void display() {
   
        System.out.println("统计信息: 平均温度 " + (sumTemp / countReadings) 
            + "°C, 最高温度 " + maxTemp + "°C, 最低温度 " + minTemp + "°C");
    }
}

// 具体观察者 - 天气预报显示
public class ForecastDisplay implements Observer {
   
    private float currentPressure = 29.92f;
    private float lastPressure;
    private Subject weatherData;
    
    public ForecastDisplay(Subject weatherData) {
   
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }
    
    @Override
    public void update(float temperature, float humidity, float pressure) {
   
        lastPressure = currentPressure;
        currentPressure = pressure;
        display();
    }
    
    public void display() {
   
        System.out.print("天气预报: ");
        if (currentPressure > lastPressure) {
   
            System.out.println("天气正在好转!");
        } else if (currentPressure == lastPressure) {
   
            System.out.println("天气状况保持不变");
        } else {
   
            System.out.println("天气可能转凉,注意保暖");
        }
    }
}

最后,创建一个测试类来演示观察者模式:

public class WeatherStation {
   
    public static void main(String[] args) {
   
        // 创建主题
        WeatherData weatherData = new WeatherData();
        
        // 创建观察者
        CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
        StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
        ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
        
        // 模拟气象数据变化
        System.out.println("第一次气象数据更新:");
        weatherData.setMeasurements(25.0f, 65.0f, 30.4f);
        
        System.out.println("\n第二次气象数据更新:");
        weatherData.setMeasurements(27.0f, 70.0f, 29.2f);
        
        System.out.println("\n第三次气象数据更新:");
        weatherData.setMeasurements(20.0f, 90.0f, 29.2f);
        
        // 移除一个观察者
        System.out.println("\n移除统计信息显示后:");
        weatherData.removeObserver(statisticsDisplay);
        weatherData.setMeasurements(22.0f, 80.0f, 31.0f);
    }
}

输出结果:

第一次气象数据更新:
当前状况: 温度 25.0°C, 湿度 65.0%
统计信息: 平均温度 25.0°C, 最高温度 25.0°C, 最低温度 25.0°C
天气预报: 天气状况保持不变

第二次气象数据更新:
当前状况: 温度 27.0°C, 湿度 70.0%
统计信息: 平均温度 26.0°C, 最高温度 27.0°C, 最低温度 25.0°C
天气预报: 天气可能转凉,注意保暖

第三次气象数据更新:
当前状况: 温度 20.0°C, 湿度 90.0%
统计信息: 平均温度 24.0°C, 最高温度 27.0°C, 最低温度 20.0°C
天气预报: 天气状况保持不变

移除统计信息显示后:
当前状况: 温度 22.0°C, 湿度 80.0%
天气预报: 天气正在好转!

6. 观察者模式的进阶实现

推模型 vs 拉模型

观察者模式有两种常见的实现方式:推模型和拉模型。

6.1 推模型(Push Model)

在推模型中,主题对象向观察者推送所有数据,不管观察者是否需要。上面的例子使用的就是推模型。

6.2 拉模型(Pull Model)

在拉模型中,主题仅通知观察者有数据更新,由观察者自己决定获取哪些数据。下面我们将上面的例子修改为拉模型:

首先,修改观察者接口,update方法不再传递数据:

// 观察者接口 - 拉模型
public interface Observer {
   
    void update();
}

修改主题接口,添加获取数据的方法:

// 主题接口 - 拉模型
public interface Subject {
   
    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers();
    
    // 获取数据的方法
    float getTemperature();
    float getHumidity(<
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

全栈凯哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值