当前位置: 首页 >  网技达人 >  观察者模式——学习笔记

观察者模式——学习笔记

导读:观察者模式.介绍.观察者模式是极其重要的一个设计模式,在许多框架都使用了,以及实际开发中也会用到。.定义对象之间的一种一对多的依赖关系.,使得每当一个对象的状态发生变化时,其相关的依赖对象都可以得到通知并被自动更新。主要用于多个不同的对象对一个对象的某个方法会做出不同的反应!.以

观察者模式

介绍

观察者模式是极其重要的一个设计模式,在许多框架都使用了,以及实际开发中也会用到。

定义对象之间的一种一对多的依赖关系 ,使得每当一个对象的状态发生变化时,其相关的依赖对象都可以得到通知并被自动更新。主要用于多个不同的对象对一个对象的某个方法会做出不同的反应!

以不同观察者从同一个天气台中获取数据为例,实践一下简单而普通的观察者模式实现。在(23GoF)书中也有这个例子,这里借鉴了Observable 进行了一些修改。

其核心在于理解它的定义、使用场景、以及四个重要的方法:

  • addObserver(Observer o);
  • addObserver(Observer o);
  • addObserver(Observer o);
  • update(Object o);

update 由 观察者实现,其余的由主题实现。

代码示例

1. Subject 主题接口

/**
 * @Author CNDA
 * @Date 2023/3/14 8:42
 * @ClassName: Subject
 * @Description: Subject 主题,一旦有改变则推送给所有已经订阅的观察者或者观察者自己拉取数据
 * 注意:这个方案只是做学习,并不适用于多线程以及同步的情况。
 * @Version 1.0
 */
public interface Subject {
    void addObserver(Observer o); // 添加观察者到列表中

    void removeObserver(Observer o); // 从列表中输出观察者

    void notifyObserver(Object arg); // 将 arg 的数据发送给所有观察者

    int size(); // 返回当前主题的所有订阅者个数
}

2. Observer 观察者接口/抽象类 这里是接口实现

public interface Observer {
    /**
     * 当主题有变化,会通知所有订阅的 Observer
     * @param arg 主题变化的数据
     * 观察者可以主动 ”拉取“ 或者 由主题 ”推送“
     */
    void update(Object arg);
}

3. 实现 Subject 接口类——WeatherData 类

天气主题为例。

public class WeatherData implements Subject {
    private float temperature;
    private float humidity;
    private float light;
    private float co_2;
    private boolean isPush = true; // true 为主动推送,false 为订阅者调用对应getxxx() 方法拉取数据。



    // 观察者列表
    private final List<Observer> observers;

    // 无参构造
	public WeatherData() {
        this.observers = new ArrayList<>();
    }

    // 提供 getter() 给观察者拉取数据。
    public float getTemperature() {
        return temperature;
    }

    public float getHumidity() {
        return humidity;
    }

    public float getLight() {
        return light;
    }

    public float getCo_2() {
        return co_2;
    }

    public void setPush(boolean isPush){
        this.isPush = isPush;
    }


    @Override
    public void addObserver(Observer o) {
        observers.add(o);
    }

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

    @Override
    public void notifyObserver(Object arg) {
        // 通知 observers 的所有观察者
        for (Observer observer : observers) {
            observer.update(arg);
        }
    }

    // 设置内容、更新数据。
    public void setManager(float temperature, float humidity, float light, float co_2){
        if (isPush){
            Map<String,Float> pramMap = new HashMap<>();
            pramMap.put("temperature",temperature);
            pramMap.put("humidity",humidity);
            pramMap.put("light",light);
            pramMap.put("co_2",co_2);
            push(pramMap);
        }else {
            this.temperature = temperature;
            this.humidity = humidity;
            this.light = light;
            this.co_2 = co_2;
            pull();
        }
    }

    public void pull(){
        notifyObserver(null);
    }

    public void push(Map<String,Float> map){
        notifyObserver(map);
    }

    @Override
    public int size() {
        return observers.size();
    }
	// 清理 Observers
    public void clearObservers(){
        observers.clear();
    }
}

通常基础的观察者模式的落地实现,一般会有一个变量控制:booble change。

change 表示标记已经改变的事实。

在 java.util.Observable 中就有一个这个变量,其主要作用是由主题来规定是否更新数据。

可能有些值需要一定的量变才能代表其数据价值。并不是每次变动都要通知所有订阅者更新数据。

而观察者更新数据的方式又分为 主动推送(push) 和 观察者拉取(pull):

如果有一定量的数据,并不是每个观察者都需要主题推送所有数据,可能只需要个别数据,这个时候就可以向外部暴露接口,让观察者拉取所需要的数据。

4. 实现 Observer 接口类 —— DefaultObserver、BaseObserver

4.1 DisplayElement

public interface DisplayElement {
    void display();
}

所有 Observer 都需要实现这个方法,用于显示打印不同观察者的内容。代表观察者的数据内容展示。

4.2 BaseObserver

使用的是推送 数据。

public class BaseObserver implements Observer, DisplayElement {
    private float temperature;
    private float humidity;
    private float light;
    private float co_2;

    private Subject subject;

    @Override
    public void update(Object arg) {
        // 解析 arg
        if (arg instanceof HashMap){
            Map<String, Float> map = (Map<String, Float>) arg;
            this.temperature = map.get("temperature");
            this.co_2 = map.get("co_2");
            this.light = map.get("light");
            this.humidity = map.get("humidity");
        }
        display();

    }


    @Override
    public void display() {
        System.out.println("BaseObserver Context temperature : " + this.temperature
                + " humidity : " + this.humidity + " light : " + this.light + " Co_2 : " + this.co_2);
    }

    public BaseObserver(Subject subject) {
        this.subject = subject;
    }

    // 取消订阅
    public void unsubscribe() {
        subject.removeObserver(this);
    }

	// 设置主题
    public void setSubject(Subject subject) {
        this.subject = subject;
    }

}

4.3 DefaultObserver

使用的是拉取 数据

public class DefaultObserver implements Observer, DisplayElement {
    private float temperature;
    private float humidity;
    private  Subject subject;

    public DefaultObserver(Subject subject) {
        this.subject = subject;
    }

    @Override
    public void update(Object arg) {
        // 如果 arg 是 null 则主动拉取
        if (subject instanceof WeatherData) {
            WeatherData weatherData = (WeatherData) subject;
            this.temperature = weatherData.getTemperature();
            this.humidity = weatherData.getHumidity();
        }
        display();
    }

    public void unsubscribe() {
        subject.removeObserver(this);
    }
    // 重置 Subject 引用
    public void setSubject(Subject subject) {
        this.subject = subject;
    }

    @Override
    public void display() {
        System.out.println("DefaultObserver Context temperature : " + this.temperature + " humidity : " + this.humidity);
    }
}

5. 测试效果

@Test
public void test01(){
    // 主题
    WeatherData weatherData = new WeatherData();

    // 准备两个观察者
    DefaultObserver observer = new DefaultObserver(weatherData);
    BaseObserver baseObserver = new BaseObserver(weatherData);

    // 将观察者订阅到列表中
    weatherData.addObserver(observer);
    weatherData.addObserver(baseObserver);

    // 主题更新数据
    weatherData.setManager(32.5f,20.6f,155.6f,16.9f);
    weatherData.setManager(30.2f,26.9f,504.6f,19.9f);
    weatherData.setManager(28.9f,10.6f,400.1f,30.9f);
}

已知主题默认是推送:

只有 BaseObserver 才有数据更新。

设置 weatherData.setPush(false); 改为由观察者拉取数据。

注意:上面将拉取和推送单独分开了。从而导致在推送时,拉取不到新的数据,反之亦然。

5.1 测试观察者取消订阅

// 主题更新数据
weatherData.setManager(32.5f,20.6f,155.6f,16.9f);
observer.unsubscribe();
weatherData.setManager(30.2f,26.9f,504.6f,19.9f);
weatherData.setManager(28.9f,10.6f,400.1f,30.9f);

结果符合预期。

5.2 测试主题清空观察者列表

// 主题更新数据
weatherData.setManager(32.5f,20.6f,155.6f,16.9f);
weatherData.clearObservers();
weatherData.setManager(30.2f,26.9f,504.6f,19.9f);
weatherData.setManager(28.9f,10.6f,400.1f,30.9f);

JDK 中的 Observable 可观察者

上面的代码是在学习了观察者模式以及 Observable 之后进行一些修改的。

JDK 中有观察者模式的实现方案:ObservableObserver。但是在 JDK 9 中被废弃了,因为存在一些问题。其中最主要的是 Observable 是一个类,不易于扩展,以及各种安全问题。而 Observer 又依赖于 Observable

所以这些问题就是 Observable 的黑暗面。

在 Swing 中,也有运用到过观察者模式。

总结

简单来说观察者模式是典型的一对多 结构。观察者模式可以方便统一的更新订阅者的数据,观察者可以从主题中获取对应的数据,不关心数据如何产生,以及如何传递,只关心如何使用这些数据。而主题只维护好观察者列表,当数据更新时根据业务情况去通知所有订阅的观察者。达到松耦合、可扩展、易维护的目的。

自己对于模式的一个浅层理解和简单代码实现,可以根据自己的想法围绕着该模式的定义进行设计,也可以看其他优秀的博客和优质的代码,学习精华和思想。

内容
  • 最新消息,powershell,10多年的癌症被治好了!
    最新消息,powershell,
    2023-12-10
    问:癌症是指什么?.答:.powershell一直有个特性,它的管道会传递对象,请看:.‘abc’ | ForEach-
  • RocketMQ消费者是如何负载均衡的
    RocketMQ消费者是如何负载
    2023-12-09
    摘要:RocketMQ 支持两种消息模式:集群消费( Clustering )和广播消费( Broadcasting )
  • Three.js教程:对象克隆、复制
    Three.js教程:对象克隆、
    2023-12-08
    推荐:将 NSDT场景编辑器 加入你的3D工具链.其他系列工具: NSDT简石数字孪生.对象克隆.clone()和复制.
  • FlashDuty Changelog 2023-09-07 | 新增深色模式与主题配置
    FlashDuty Change
    2023-12-04
    FlashDuty:一站式告警响应平台,前往此地址免费体验!.FlashDuty.现在已经全面支持了深色模式,这为您提供
  • 时尚个性针织毛衣
    时尚个性针织毛衣
    2023-12-11
    时尚个性针织毛衣.时尚个性针织毛衣一直是秋冬季节的必备单品,不仅可以很好地保暖,还能展现出个性与时尚。无论是女性还是男性
  • 休闲简约短袖衬衫
    休闲简约短袖衬衫
    2023-12-21
    休闲简约短袖衬衫.现代人生活节奏快,休闲简约的穿着成为时尚潮流。短袖衬衫作为经典的休闲单品,一直备受时尚人士的青睐。它舒
  • 经典款皮鞋
    经典款皮鞋
    2023-12-06
    经典款皮鞋.经典款皮鞋一直是时尚界的永恒之选,不论是商务场合、休闲聚会还是正式场合,都能展现出绅士淑女的气质和优雅。今天
  • 修身弹力牛仔裤
    修身弹力牛仔裤
    2023-12-26
    修身弹力牛仔裤:展现你的魅力.一、时尚的必备单品.修身弹力牛仔裤一直都是时尚界的必备单品,它不仅可以展现出个人的魅力,还
  • 可爱儿童内衣套装,优质棉质,柔软透气,呵护宝宝肌肤
    可爱儿童内衣套装,优质棉质,柔软
    2024-01-05
    可爱儿童内衣套装,优质棉质,柔软透气,呵护宝宝肌肤.宝宝的皮肤是非常娇嫩的,所以选择合适的内衣套装对于宝宝的健康和舒适至
  • 优雅复古半身裙,散发优雅复古气息
    优雅复古半身裙,散发优雅复古气息
    2024-01-15
    优雅复古半身裙,散发优雅复古气息.复古是一种永不过时的时尚趋势,它总能让人们联想到过去的美好时光。而半身裙则是女性衣橱里
  • 时尚修身连衣裙,展现优雅女性魅力
    时尚修身连衣裙,展现优雅女性魅力
    2023-12-06
    时尚修身连衣裙,展现优雅女性魅力.时尚修身连衣裙一直是女性衣橱里的必备单品,不仅款式多样,而且能够展现出女性的优雅魅力。
  • 潮流风衣大衣,彰显都市时尚风采
    潮流风衣大衣,彰显都市时尚风采
    2023-12-16
    潮流风衣大衣,彰显都市时尚风采.潮流风衣大衣一直是时尚界备受追捧的单品之一。它既能为我们遮风挡雨,又能为我们穿出时尚感,
  • 暖心家居服套装,柔软舒适,可爱**形象,让宝宝安心入睡
    暖心家居服套装,柔软舒适,可爱*
    2023-12-16
    暖心家居服套装,让宝宝安心入睡.宝宝的睡眠质量对成长发育至关重要,而穿着舒适的家居服对宝宝的睡眠质量有着直接的影响。为了
  • 时尚儿童牛仔裤,经典款式,耐穿耐磨,让宝宝更有个性
    时尚儿童牛仔裤,经典款式,耐穿耐
    2024-01-10
    时尚儿童牛仔裤引领潮流.时尚儿童牛仔裤一直是儿童服装中的经典款式,不仅经典耐穿,而且可以展现宝宝的个性。随着时尚的发展,