1、观察者模式设计初衷

1.1. 解耦对象之间的依赖关系

在软件系统中,对象之间往往需要相互依赖。例如,视图对象依赖于数据模型对象,来显示最新的数据状态。直接的依赖关系会导致高耦合,增加维护的复杂性和难度。

设计初衷

  • 松耦合设计:观察者模式通过抽象层(Observer接口)来解耦具体的对象。被观察者(Subject)只知道有观察者存在,不需要了解观察者的具体实现。
  • 易于扩展:通过接口解耦,可以方便地增加新的观察者而不影响现有的代码,实现开闭原则(对扩展开放,对修改关闭)。

示例
一个天气预报系统中,WeatherData类作为被观察者,而CurrentConditionsDisplayStatisticsDisplay等作为观察者。WeatherData不需要了解具体显示类的实现,只需知道它们实现了Observer接口。

1.2. 允许动态的依赖关系

在运行时,系统的依赖关系可能需要动态变化。例如,用户可能会在运行时订阅或取消订阅某些数据。

设计初衷

  • 动态注册和注销观察者:观察者模式允许在运行时动态地添加和移除观察者,灵活应对变化的需求。
  • 灵活性和动态性:被观察者可以在任何时候通知其观察者,无需事先确定所有的观察者。

示例
在一个股票交易系统中,用户可以随时订阅或取消订阅股票行情。StockData类作为被观察者,当用户订阅时,将其客户端注册为观察者,当用户取消订阅时,将其从观察者列表中移除。

1.3. 自动通知和更新

在一些实时更新的系统中,依赖对象需要在状态变化时自动更新。例如,当数据模型改变时,视图对象需要自动更新以反映最新的数据。

设计初衷

  • 自动通知:被观察者在其状态改变时,自动通知所有注册的观察者,避免手动更新带来的复杂性。
  • 一致性:确保所有依赖于被观察者的对象在状态变化后保持一致性,避免数据不同步问题。

示例
在一个气象站应用中,当气象数据变化时,WeatherData会通知所有的显示组件(观察者),以便它们更新显示最新的气象数据。

1.4 设计初衷的详细说明

1. 对象之间的解耦

问题
在软件系统中,不同的对象之间往往存在依赖关系。例如,一个数据模型对象(Model)和多个视图对象(View)。当数据模型发生变化时,视图对象需要随之更新。如果每个视图对象都直接依赖于数据模型对象,并且在数据模型中直接调用视图对象的方法,这将导致系统的高耦合,增加维护难度。

解决方案
通过观察者模式,被观察者(Subject)和观察者(Observer)之间的关系是松耦合的。被观察者只知道有一组观察者实现了某个接口,而不知道具体是什么对象。观察者也只知道被观察者的状态发生了变化,而不知道变化的具体原因。这样就实现了对象之间的解耦。

示例
在一个气象站应用中,气象数据(WeatherData)是被观察者,而各种显示元素(如当前条件显示、统计显示等)是观察者。气象数据只需要通知观察者数据发生了变化,而具体如何显示是观察者的事情。

2. 动态依赖关系

问题
系统在运行时可能需要动态地添加或移除依赖关系。例如,在一个实时股票行情系统中,不同的客户端可能在不同的时间段订阅或取消订阅股票行情。

解决方案
观察者模式允许在运行时动态地注册(添加)或注销(移除)观察者。这使得系统能够灵活地响应不同的需求和变化,而无需在设计时确定所有的依赖关系。

示例
在一个聊天应用中,当用户加入一个群组时,用户的客户端会成为群组消息的观察者。当用户离开群组时,用户的客户端会被移除出群组消息的观察者列表。

3. 自动更新

问题
当一个对象的状态变化时,依赖它的所有对象需要同步更新。例如,在一个数据驱动的应用中,当数据模型变化时,所有依赖于该模型的视图都需要更新。

解决方案
通过观察者模式,当被观察者的状态发生变化时,它会通知所有注册的观察者进行更新。这样,所有依赖于被观察者的对象都能够自动更新,保持系统的一致性。

示例
在一个股票交易应用中,当股票价格变化时,所有显示该股票价格的视图都会自动更新,以显示最新的价格。

2、实现细节

2.1. Subject 接口和实现

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

public interface Subject {
    void registerObserver(Observer o);
    void removeObserver(Observer o);
    void notifyObservers();
}

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 o) {
        observers.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        observers.remove(o);
    }

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

    public void setMeasurements(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }

    public void measurementsChanged() {
        notifyObservers();
    }
}

2.2. Observer 接口和实现

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

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("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity");
    }
}

2.3. 主类

public class WeatherStation {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();
        CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);

        weatherData.setMeasurements(80, 65, 30.4f);
        weatherData.setMeasurements(82, 70, 29.2f);
        weatherData.setMeasurements(78, 90, 29.2f);
    }
}

3、主要角色

  1. Subject(被观察者/主题)

    • 保存观察者的列表。
    • 向观察者发送通知。
    • 提供注册和移除观察者的方法。
  2. Observer(观察者)

    • 接收通知并更新状态。
    • 包含一个更新方法,用于被主题通知。

4、关系示意图

+----------+        +-----------+
|  Subject |<------>|  Observer |
+----------+        +-----------+
| attach() |        | update()  |
| detach() |        +-----------+
| notify() |
+----------+

5、Java 实现示例

使用 Java 内置的 Observable 和 Observer

Java 提供了内置的 java.util.Observable 类和 java.util.Observer 接口。

Observable 类
import java.util.Observable;

public class WeatherData extends Observable {
    private float temperature;
    private float humidity;
    private float pressure;

    public void setMeasurements(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }

    public void measurementsChanged() {
        setChanged();
        notifyObservers();
    }

    public float getTemperature() {
        return temperature;
    }

    public float getHumidity() {
        return humidity;
    }

    public float getPressure() {
        return pressure;
    }
}
Observer 接口
import java.util.Observable;
import java.util.Observer;

public class CurrentConditionsDisplay implements Observer {
    private float temperature;
    private float humidity;

    public CurrentConditionsDisplay(Observable observable) {
        observable.addObserver(this);
    }

    @Override
    public void update(Observable obs, Object arg) {
        if (obs instanceof WeatherData) {
            WeatherData weatherData = (WeatherData) obs;
            this.temperature = weatherData.getTemperature();
            this.humidity = weatherData.getHumidity();
            display();
        }
    }

    public void display() {
        System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity");
    }
}
主类
public class WeatherStation {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();
        CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);

        weatherData.setMeasurements(80, 65, 30.4f);
        weatherData.setMeasurements(82, 70, 29.2f);
        weatherData.setMeasurements(78, 90, 29.2f);
    }
}

6、自定义实现 Observer 和 Subject

在现代Java编程中,更推荐使用自定义接口和类实现观察者模式,因为 java.util.Observable 已被标记为过时。

6.1 Subject 接口

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

public interface Subject {
    void registerObserver(Observer o);
    void removeObserver(Observer o);
    void notifyObservers();
}

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 o) {
        observers.add(o);
    }

    @Override
    public void removeObserver(Observer o) {
        int i = observers.indexOf(o);
        if (i >= 0) {
            observers.remove(i);
        }
    }

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

    public void setMeasurements(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }

    public void measurementsChanged() {
        notifyObservers();
    }
}

6.2 Observer 接口

public interface Observer {
    void update(float temp, float humidity, float pressure);
}

class CurrentConditionsDisplay implements Observer {
    private float temperature;
    private float humidity;

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

    public void display() {
        System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity");
    }
}

主类

public class WeatherStation {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();
        CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay();

        weatherData.registerObserver(currentDisplay);

        weatherData.setMeasurements(80, 65, 30.4f);
        weatherData.setMeasurements(82, 70, 29.2f);
        weatherData.setMeasurements(78, 90, 29.2f);
    }
}

7、优缺点分析

7.1 优点

  1. 松耦合

    • 观察者和被观察者之间是松耦合的,便于维护和扩展。
  2. 动态注册和注销

    • 可以在运行时动态添加或移除观察者,灵活应对变化的需求。
  3. 自动通知

    • 被观察者状态变化时,会自动通知所有观察者,保持系统的一致性。

7.2 缺点

  1. 通知顺序不确定

    • 观察者接收通知的顺序不确定,可能导致一些时序问题。
  2. 内存泄漏

    • 如果被观察者没有正确地移除不再需要的观察者,可能导致内存泄漏。
  3. 性能问题

    • 如果观察者数量过多,通知过程可能比较耗时,影响性能。

8、总结

观察者模式的设计初衷主要在于解决对象之间的耦合问题,允许动态依赖关系,并自动通知依赖对象进行更新。这些特性使得观察者模式在需要松耦合、动态依赖和自动更新的场景中非常有用。然而,开发者在使用观察者模式时,也需要注意内存泄漏、性能开销等问题,采取适当的优化措施,如使用弱引用和异步通知,来提升系统的健壮性和效率。

通过合理使用观察者模式,可以使系统更加灵活、可维护性更高,并且能够有效地响应变化。这种模式在GUI应用、实时数据更新、发布-订阅系统等场景中得到了广泛应用,是软件设计中的重要模式之一。

欢迎点赞|关注|收藏|评论,您的肯定是我创作的动力

在这里插入图片描述

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐