观察者模式

观察者模式(Observer Pattern) 是一种行为型设计模式,它定义了一种一对多的依赖关系,使得一个对象状态改变时,所有依赖于它的对象都能收到通知并自动更新。观察者模式的核心思想是发布-订阅机制,发布者对象称为目标(Subject),订阅者对象称为观察者(Observer)

1. 观察者模式的结构

观察者模式通常包括以下角色:

  1. 目标(Subject):也叫被观察者,它包含观察者对象的集合,并提供注册、移除观察者的方法。当自身状态发生变化时,会通知所有观察者。
  2. 观察者(Observer):定义一个更新接口,观察者对象在目标状态变化时会收到通知,并更新自身状态。
  3. 具体目标(Concrete Subject):实现目标接口或继承目标类,包含一些具体的业务状态和逻辑,当状态发生变化时,负责通知观察者。
  4. 具体观察者(Concrete Observer):实现观察者接口,实现接收到通知后的具体操作。

观察者模式的 UML 类图

plaintextCopy code+----------------+      +-------------------------+
|    Subject     |<>----|        Observer         |
+----------------+      +-------------------------+
|+ addObserver() |      |+ update()               |
|+ removeObserver()|     +------------------------+
|+ notifyObservers()|
+----------------+                               |
                        |                         |
                        |                         |
                        V                         V
        +------------------------+      +------------------------+
        | ConcreteSubject        |      | ConcreteObserver       |
        +------------------------+      +------------------------+
        |+ getState()            |      |+ update()              |
        |+ setState()            |      +------------------------+
        +------------------------+

2. 观察者模式的实现

以一个简化的天气预报系统为例,模拟一个天气站作为发布者,显示设备作为观察者。当天气站中的天气数据发生变化时,通知所有已注册的显示设备更新数据。

1. 定义观察者接口

javaCopy codepublic interface Observer {
    void update(float temperature, float humidity, float pressure);
}

2. 定义目标接口

目标接口用于管理观察者的注册、移除以及通知观察者的方法。

javaCopy codepublic interface Subject {
    void addObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObservers();
}

3. 实现具体目标(天气站)

天气站作为具体目标,当数据更新时会通知所有观察者。

javaCopy codeimport 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<>();
    }

    public void addObserver(Observer observer) {
        observers.add(observer);
    }

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

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

    public void setWeatherData(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        notifyObservers();
    }
}

4. 实现具体观察者(显示设备)

具体观察者用于实现收到通知后具体操作的逻辑。每种显示设备都可以有不同的显示方式。

javaCopy codepublic class CurrentConditionsDisplay implements Observer {
    private float temperature;
    private float humidity;
    private float pressure;

    @Override
    public void update(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        display();
    }

    public void display() {
        System.out.println("Current conditions: " + temperature + "°C, " + humidity + "% humidity, " + pressure + " Pa pressure.");
    }
}

5. 客户端代码

在客户端代码中,创建天气站(目标对象)和显示设备(观察者),并将观察者注册到目标中。每当天气站更新数据时,显示设备会自动接收到通知并更新数据。

javaCopy codepublic class Client {
    public static void main(String[] args) {
        WeatherStation weatherStation = new WeatherStation();

        Observer display1 = new CurrentConditionsDisplay();
        Observer display2 = new CurrentConditionsDisplay();

        weatherStation.addObserver(display1);
        weatherStation.addObserver(display2);

        // 更新天气数据,通知观察者
        weatherStation.setWeatherData(25.5f, 65f, 1013.1f);
        weatherStation.setWeatherData(22.3f, 70f, 1012.5f);
    }
}
输出示例
plaintextCopy codeCurrent conditions: 25.5°C, 65.0% humidity, 1013.1 Pa pressure.
Current conditions: 25.5°C, 65.0% humidity, 1013.1 Pa pressure.
Current conditions: 22.3°C, 70.0% humidity, 1012.5 Pa pressure.
Current conditions: 22.3°C, 70.0% humidity, 1012.5 Pa pressure.

3. 观察者模式的优缺点

优点

  1. 解耦:目标和观察者之间的依赖是松散的,目标只知道观察者实现了某个接口,观察者与目标也没有直接依赖关系。
  2. 动态扩展:观察者可以根据需要动态添加或移除,便于扩展和维护。
  3. 符合开闭原则:可以在不修改目标类的情况下添加新的观察者。

缺点

  1. 通知顺序不确定:多个观察者接收到通知的顺序不固定,这可能会导致一些问题,尤其在有依赖关系的观察者之间。
  2. 循环依赖:在复杂的观察链中,容易出现循环依赖的问题,导致无限循环。
  3. 性能问题:如果观察者数量较多或通知频繁,可能会带来性能开销。

4. 观察者模式的应用场景

观察者模式适用于以下场景:

  1. 事件监听:GUI 事件监听器、文件系统的事件通知等。
  2. 订阅-发布:消息系统、通知系统、日志订阅等。
  3. 数据同步:如多个数据视图需要实时更新,可以使用观察者模式来同步更新。
  4. 通知系统:如电商中的库存、价格变化通知。

5. 观察者模式的扩展与优化

1. 支持拉模型(Pull Model)

在上述实现中,观察者通过 update 方法获得数据,这是推模型(Push Model)。另一种方式是拉模型(Pull Model),即观察者只接收到通知,而在通知中不包含数据,需要时观察者主动从目标对象中获取数据。

javaCopy codepublic interface Observer {
    void update(); // 不传递数据
}

public class WeatherStation implements Subject {
    private float temperature;
    private float humidity;
    private float pressure;

    public float getTemperature() {
        return temperature;
    }

    public void setWeatherData(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        notifyObservers();
    }
}

2. 使用 Java 内置的观察者模式支持

Java 自带的 java.util.Observable 类和 java.util.Observer 接口提供了观察者模式的实现。Observable 可以作为目标对象的基类,Observer 作为观察者接口使用。

javaCopy codepublic class WeatherStation extends Observable {
    private float temperature;

    public void setTemperature(float temperature) {
        this.temperature = temperature;
        setChanged(); // 标记状态发生了变化
        notifyObservers(temperature); // 通知观察者
    }
}

public class Display implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        System.out.println("更新温度为: " + arg + "°C");
    }
}

3. 支持异步通知

在观察者数量较多或通知操作较耗时的情况下,可以将通知过程放在异步线程中,以提高性能和响应速度。这种方式通常会结合消息队列(如 Kafka、RabbitMQ)或事件驱动架构实现。

6. 总结

观察者模式通过发布-订阅的机制实现了一对多的依赖关系,目标对象在状态变化时,自动通知所有观察者。其主要优点是解耦、易扩展,适合事件驱动和数据同步场景,但在处理大量观察者或复杂依赖关系时需要小心应对性能和循环依赖问题。

0 0 投票数
Article Rating
订阅评论
提醒
guest
0 评论
最旧
最新 最多投票
内联反馈
查看所有评论
0
希望看到您的想法,请您发表评论x