每日知识-设计模式-观察者模式

之前写了每日知识-设计模式-状态机模式,今天来复习一种新的模式,观察者模式,我有的时候喜欢管它叫"订阅模式"。

什么是观察者模式?

在社交网站中,一个人关注了另外一个人,比如张三关注了李四,张三更新了动态,就会出现在李四的信息流中。这就是一种观察者模式的场景。

观察者模式是一种行为型设计模式 ,它定义了一种一对多的依赖关系。当一个对象(称为"主题"或"可观察者")的状态发生改变时,所有依赖于它的对象(称为"观察者")都会自动得到通知并被更新。

在设计模式里,具体的实现细节是,观察者把联系方式留给了被观察者,被观察者更新状态的时候,主动推送内容给观察者,观察者被动接受,不用去轮询。观察者通过声明自己观察了被观察者,或者主动向被观察者注册, 实质上留下联系方式,从而当信息更新时第一时间可以获得。

如何实现观察者模式?

观察者模式主要包含四个角色:

  1. Subject(主题) :维护一个观察者列表,提供添加(attach)、删除(detach)观察者的方法,以及通知所有观察者的方法(notify)。
  2. ConcreteSubject(具体主题) :继承自 Subject。它包含具体的状态数据,当状态改变时,调用父类的通知方法。
  3. Observer(观察者) :一个接口,通常只定义一个更新方法(update),供主题在通知时调用。
  4. ConcreteObserver(具体观察者) :实现 Observer 接口。它维护一个对 ConcreteSubject 的引用,以便在接到通知时,能获取主题的状态并做出相应的反应。

举个例子:气象站监测系统

假设我们有一个气象站(主题),它能监测温度、湿度、气压。当这些数据发生变化时,需要实时更新多个布告板(观察者),比如"当前状况布告板"、"气象统计布告板"。

1. 定义主题接口 (Subject)

java 复制代码
// 主题接口
public interface Subject {
    void registerObserver(Observer o); // 注册观察者(订阅)
    void removeObserver(Observer o);   // 移除观察者(取消订阅)
    void notifyObservers();            // 通知观察者
}

2. 定义观察者接口 (Observer)

java 复制代码
// 观察者接口
public interface Observer {
    void update(float temperature, float humidity, float pressure); // 更新方法
}

3. 实现具体主题 (ConcreteSubject) - 气象站

java 复制代码
// 具体主题:气象数据
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 o) {
        observers.add(o);
    }

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

    @Override
    public void notifyObservers() {
        // 主题遍历所有观察者,调用它们的update方法
        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(); // 数据改变,通知观察者
    }

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

4. 实现具体观察者 (ConcreteObserver) - 布告板

java 复制代码
// 具体观察者:当前状况布告板
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 + "% 湿度");
    }
}

5. 测试使用

java 复制代码
public class WeatherStation {
    public static void main(String[] args) {
        // 1. 创建主题(气象站)
        WeatherData weatherData = new WeatherData();

        // 2. 创建观察者(布告板),并在创建时完成订阅
        CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);
        // 可以轻松添加更多布告板...
        // StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);

        // 3. 模拟新的气象测量数据到达
        System.out.println("模拟新数据...");
        weatherData.setMeasurements(26.5f, 65f, 1013.1f);

        System.out.println("\n再次模拟新数据...");
        weatherData.setMeasurements(28.1f, 70f, 1012.5f);
    }
}

输出结果:

erlang 复制代码
模拟新数据...
当前状况: 26.5°C 和 65.0% 湿度

再次模拟新数据...
当前状况: 28.1°C 和 70.0% 湿度

类之间的关系

通过上面的例子可以看出来:

  • 观察者持有被观察者的引用
  • 被观察者持有观察者引用的列表
  • 被观察者负责通知观察者,需要实现通知方法,提供注册和注销的接口方法,当然还有更新数据的方法,在更新数据的方法内部,需要调用通知方法。
  • 观察者提供一个观察者可以被调用的接口方法,用来由被观察者向其推送消息。

双方互相持有对方的引用,被观察者作为1对N的角色,不会特别在意观察者的数量等。

优点与缺点

优点:

  • 开闭原则:你可以引入新的观察者类,而无需修改主题代码(对扩展开放)。
  • 松耦合:主题只知道观察者实现了某个接口,不知道观察者的具体类是谁、做了什么。它们可以独立变化。

缺点:

  • 通知顺序 :观察者的通知顺序是随机的(例如上面用的 ArrayList),不应依赖特定顺序。
  • 性能开销:如果观察者数量非常多,逐个通知会耗时较长。如果更新操作代价高,可能导致性能问题。
相关推荐
努力也学不会java18 小时前
【设计模式】三大原则 单一职责原则、开放-封闭原则、依赖倒转原则
java·设计模式·依赖倒置原则·开闭原则·单一职责原则
青鱼入云19 小时前
【面试场景题】如何理解设计模式
设计模式·面试·职场和发展
YA3331 天前
java设计模式一、单例模式
java·单例模式·设计模式
郝学胜-神的一滴1 天前
Pomian语言处理器研发笔记(二):使用组合模式定义表示程序结构的语法树
开发语言·c++·笔记·程序人生·决策树·设计模式·组合模式
TechNomad1 天前
设计模式:代理模式(Proxy Pattern)
设计模式·代理模式
幸幸子.2 天前
【设计模式】--重点知识点总结
设计模式
刘火锅2 天前
设计模式-状态模式 Java
java·设计模式·状态模式
九转苍翎2 天前
Java内功修炼(3)——并发的四重境界:单例之固、生产消费之衡、定时之准、池化之效
java·设计模式·thread
TechNomad2 天前
设计模式:享元模式(Flyweight Pattern)
设计模式·享元模式