气象站(WeatherStation) 是被观察者(Subject),它会持续监测天气数据(温度、湿度、气压)。当数据发生变化时,它需要通知所有订阅了它的显示器(Display),比如当前 conditions 显示器、统计数据显示器和天气预报显示器。这些显示器就是观察者(Observer)。
步骤 1:定义被观察者(Subject)接口
这个接口定义了被观察者需要具备的核心能力:注册观察者、移除观察者、通知观察者。
import java.util.List;
/**
* 被观察者(Subject)接口
*/
public interface Subject {
/**
* 注册一个观察者
* @param observer 要注册的观察者
*/
void registerObserver(Observer observer);
/**
* 移除一个观察者
* @param observer 要移除的观察者
*/
void removeObserver(Observer observer);
/**
* 当主题状态改变时,通知所有注册的观察者
*/
void notifyObservers();
}
步骤 2:定义观察者(Observer)接口
这个接口定义了观察者的核心能力:接收并处理来自被观察者的通知。
java
运行
/**
* 观察者(Observer)接口
*/
public interface Observer {
/**
* 当被观察者状态改变时,会调用此方法通知观察者
* @param temperature 温度
* @param humidity 湿度
* @param pressure 气压
*/
void update(float temperature, float humidity, float pressure);
}
步骤 3:定义显示器(DisplayElement)接口 (可选,但推荐)
为了统一所有显示器的展示行为,我们可以创建一个 接口。
DisplayElement
java
运行
/**
* 显示器接口,所有显示器都应实现此接口
*/
public interface DisplayElement {
/**
* 展示当前数据
*/
void display();
}
步骤 4:实现具体的被观察者(ConcreteSubject)
这里我们实现 类,它负责管理天气数据和观察者列表。
WeatherStation
java
运行
import java.util.ArrayList;
import java.util.List;
/**
* 具体的被观察者:气象站
*/
public class WeatherStation implements Subject {
// 存储所有注册的观察者
private List<Observer> observers;
// 气象数据
private float temperature;
private float humidity;
private float pressure;
public WeatherStation() {
// 初始化观察者列表
observers = new ArrayList<>();
}
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
// 遍历所有观察者,并调用它们的 update 方法
for (Observer observer : observers) {
observer.update(temperature, humidity, pressure);
}
}
/**
* 当气象站检测到新数据时,调用此方法
* @param temperature 温度
* @param humidity 湿度
* @param pressure 气压
*/
public void measurementsChanged(float temperature, float humidity, float pressure) {
System.out.println("
WeatherStation: Measurements updated. Notifying observers...");
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
// 数据改变,通知所有观察者
notifyObservers();
}
// 模拟气象站获取数据的方法
public void setMeasurements(float temperature, float humidity, float pressure) {
measurementsChanged(temperature, humidity, pressure);
}
}
步骤 5:实现具体的观察者(ConcreteObserver)
现在我们来实现几个具体的显示器。
1. 当前 conditions 显示器
java
运行
/**
* 具体的观察者:当前天气状况显示器
*/
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
// 可选:持有一个对 Subject 的引用,以便后续可以取消订阅
private Subject weatherStation;
public CurrentConditionsDisplay(Subject weatherStation) {
this.weatherStation = weatherStation;
// 在构造函数中自动注册自己为观察者
weatherStation.registerObserver(this);
}
@Override
public void update(float temperature, float humidity, float pressure) {
// 更新内部状态
this.temperature = temperature;
this.humidity = humidity;
// 当数据更新后,立即展示
display();
}
@Override
public void display() {
System.out.println("Current Conditions Display: " + temperature + "F degrees and " + humidity + "% humidity");
}
}
2. 统计数据显示器
java
运行
/**
* 具体的观察者:统计数据显示器
*/
public class StatisticsDisplay implements Observer, DisplayElement {
private float maxTemp = 0.0f;
private float minTemp = 200;
private float tempSum = 0.0f;
private int numReadings;
public StatisticsDisplay(Subject weatherStation) {
weatherStation.registerObserver(this);
}
@Override
public void update(float temperature, float humidity, float pressure) {
tempSum += temperature;
numReadings++;
if (temperature > maxTemp) {
maxTemp = temperature;
}
if (temperature < minTemp) {
minTemp = temperature;
}
display();
}
@Override
public void display() {
System.out.println("Statistics Display: Avg/Max/Min temperature = " + (tempSum / numReadings) + "/" + maxTemp + "/" + minTemp);
}
}
3. 天气预报显示器
java
运行
/**
* 具体的观察者:天气预报显示器
*/
public class ForecastDisplay implements Observer, DisplayElement {
private float currentPressure = 29.92f;
private float lastPressure;
public ForecastDisplay(Subject weatherStation) {
weatherStation.registerObserver(this);
}
@Override
public void update(float temperature, float humidity, float pressure) {
lastPressure = currentPressure;
currentPressure = pressure;
display();
}
@Override
public void display() {
System.out.print("Forecast Display: ");
if (currentPressure > lastPressure) {
System.out.println("Improving weather on the way!");
} else if (currentPressure == lastPressure) {
System.out.println("More of the same.");
} else if (currentPressure < lastPressure) {
System.out.println("Watch out for cooler, rainy weather.");
}
}
}
步骤 6:客户端代码(使用观察者模式)
最后,我们来创建一个 类来演示整个系统的工作流程。
Main
java
运行
/**
* 客户端应用
*/
public class WeatherStationDemo {
public static void main(String[] args) {
// 1. 创建被观察者:气象站
WeatherStation weatherStation = new WeatherStation();
// 2. 创建观察者,并将它们注册到气象站
CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherStation);
StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherStation);
ForecastDisplay forecastDisplay = new ForecastDisplay(weatherStation);
// 3. 模拟气象站数据更新
System.out.println("--- First measurement ---");
weatherStation.setMeasurements(80, 65, 30.4f);
System.out.println("
--- Second measurement ---");
weatherStation.setMeasurements(82, 70, 29.2f);
System.out.println("
--- Third measurement ---");
weatherStation.setMeasurements(78, 90, 29.2f);
// 4. 演示如何移除一个观察者
System.out.println("
--- Removing StatisticsDisplay ---");
weatherStation.removeObserver(statisticsDisplay);
System.out.println("
--- Fourth measurement ---");
weatherStation.setMeasurements(62, 95, 28.1f);
}
}
运行结果
plaintext
--- First measurement ---
WeatherStation: Measurements updated. Notifying observers...
Current Conditions Display: 80.0F degrees and 65.0% humidity
Statistics Display: Avg/Max/Min temperature = 80.0/80.0/80.0
Forecast Display: Improving weather on the way!
--- Second measurement ---
WeatherStation: Measurements updated. Notifying observers...
Current Conditions Display: 82.0F degrees and 70.0% humidity
Statistics Display: Avg/Max/Min temperature = 81.0/82.0/80.0
Forecast Display: Watch out for cooler, rainy weather.
--- Third measurement ---
WeatherStation: Measurements updated. Notifying observers...
Current Conditions Display: 78.0F degrees and 90.0% humidity
Statistics Display: Avg/Max/Min temperature = 80.0/82.0/78.0
Forecast Display: More of the same.
--- Removing StatisticsDisplay ---
--- Fourth measurement ---
WeatherStation: Measurements updated. Notifying observers...
Current Conditions Display: 62.0F degrees and 95.0% humidity
Forecast Display: Watch out for cooler, rainy weather.
总结
通过这个例子,我们可以清晰地看到观察者模式的几个关键部分:
松耦合: 不知道具体的观察者是谁(
WeatherStation、
CurrentConditionsDisplay 等),它只知道它们实现了
StatisticsDisplay 接口。同样,观察者也不知道被观察者的内部细节,只关心它收到的数据。可扩展性:如果我们想添加一个新的显示器(比如 “紫外线指数显示器”),只需要创建一个新的类实现
Observer 和
Observer 接口,然后在
DisplayElement 中注册即可。不需要修改任何现有代码。动态关系:观察者可以在任何时候注册或取消注册,这种关系是动态的。
WeatherStation
JDK 中也内置了观察者模式的支持,即 类和
java.util.Observable 接口。不过,上面我们手动实现的方式更有助于理解其核心思想。在现代 Java 开发中,更推荐使用如 Google Guava 的
java.util.Observer 或 Spring 的
EventBus 等事件驱动模型,它们提供了更强大和灵活的观察者模式实现。
ApplicationEvent


