【三】Java 设计模式学习记录:观察者模式

一、观察者模式(行为型模式)

1.1 场景

有这么个项目需求,直接使用尚硅谷的例子(同理的也可以使用什么新闻中心,发布消息这样的例子)
在这里插入图片描述

1.2 普通解决方案

不用设计模式有个方案简单说下就是气象站提供各种获取的接口,当有数据更新,气象站会更新数据(或者推送给其他网站),之后通过getXX获取的数据都是最新的。

简单来说呢,就是一个数据源(气象站),一个需要获取数据的网站(就当是小米天气),气象站会引入小米天气,在数据更新的时候,去调用小米天气的方法把最新的信息推送给他。

这样的方法问题是

  1. 当使用气象局的第三方网站越来越多,需要引入的网站实例(需要创建对应网站的对象)也就越来越多
  2. 无法动态添加第三方(因为是代码里写死引入的)
  3. 违反OCP原则(对扩展开发,对修改关闭)
1.3 观察者模式定义

定义对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于 它的对象都会得到通知并被自动更新。

  • Subject 被观察者
    • 定义被观察者必须实现的职责,它必须能够动态地增加、取消观察者。它一般是抽 象类或者是实现类,仅仅完成作为被观察者必须实现的职责:管理观察者并通知观 察者。
  • Observer 观察者
    • 观察者接收到消息后,即进行 update(更新方法)操作,对接收到的信息进行处 理。
  • ConcreteSubject 具体的被观察者
    • 定义被观察者自己的业务逻辑,同时定义对哪些事件进行通知。
  • ConcreteObserver 具体的观察者
    • 每个观察在接收到消息后的处理反应是不同,各个观察者有自己的处理逻辑。
1.4 观察者模式原理

我们的气象局,或者说报社,可以说是被观察者
在这里插入图片描述
他有对应的方法,比如报社,需要知道哪些人订了报纸,订了也可以退订,当数据更新(有新报纸)了,就采取对应的通知方法(报社就是到了发短信,气象局就推送新的气象信息)
在这里插入图片描述
气象站/报社和用户之间的关系,正常来说是一对多,一个报社或者气象站会对应多个用户
在这里插入图片描述
从这里我们看到了类似于装饰者模式的4个角色

  1. 被观察者抽象,定义了管理观察者的方法
  2. 被观察者实现,具体的数据源,组合观察者列表
  3. 观察者抽象: 定义了观察者(订阅者)获取信息后处理的方法
  4. 观察者实现: 各个观察者实现观察者接口,通过接口定义的方法传入的信息,获取各自所需的值(可以少获取其实就是获取了不处理)

二、代码实现

2.1 代码结构

在这里插入图片描述
这里研究的是对象一(被观察者)对多(观察者)的关系

2.2 上代码

1.被观察者接口,这里是Subject


    /**
     * 注册观察者到观察者列表
     * @param observer
     */
    public void registerObserver(Observer observer);

    /**
     * 移除观察者从观察者列表
     * @param observer
     */
    public void removeObserver(Observer observer);
    /**
     * 通知所有观察者列表
     */
    public void notifyObservers();

2.被观察者实现,这里是天气对应的气象站,也可以是报纸订阅的报社

/**
 * 被观察者数据源具体实现 - 这里可以理解为气象站
 */
@Data
public class WeatherData implements Subject {
    // 气象站提供的信息
    // 温度 
    private double temperature;
    // 气压
    private double presure;
    // 湿度
    private double humidity;
    // 观察者(订阅)列表
    private List<Observer> observers;

    // 构造方法初始化列表 - 组合
    public WeatherData(){
        observers = new ArrayList<>();
    }

    /**
     * 获取到最新的数据,设置并通知订阅者列表
     * @param temperature 
     * @param presure
     * @param humidity
     */
    public void setData(double temperature, double presure, double humidity) {
        this.temperature = temperature;
        this.presure = presure;
        this.humidity = humidity;
        notifyObservers();
    }
    
    @Override
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }
    
    @Override
    public void removeObserver(Observer observer) {
        if (observers.contains(observer)){
            observers.remove(observer);
        }
    }

    @Override
    public void notifyObservers() {
        for (Observer observer: observers){
            observer.update(getTemperature(),getPresure(),getHumidity());
        }
    }
}

3.观察者接口,这里定义观察者获取到数据后的操作方法

/**
 * 观察者抽象
 */
public interface Observer {
    public void update(double temperature, double presure, double humidity);
}

4.观察者实现,这里是具体调用数据的站点(应用),只列举一个 - 小米
通过这张图,我们知道会有多个订阅者,我们这里只实现一个(别的基本一样,就是输出前缀不一)
(可以不建立对应成员变量,直接使用实现方法参数传来的数据进行处理 - 详情见扩展性)
在这里插入图片描述

/**
 * 具体的观察者实现
 */
public class XiaoMiObserver implements Observer {
    private double temperature;
    private double presure;
    private double humidity;
    @Override
    public void update(double temperature, double presure, double humidity) {
        // 这里实现了观察者接口方法,并执行了获取数据后的操作 -打印
        this.temperature = temperature;
        this.presure = presure;
        this.humidity = humidity;
        display();
    }

    public void display(){
        System.out.println(String.format("[小米天气]今天的温度为%s, 气压:%s,湿度是%s", temperature, presure, humidity));
    }
}

5.准备完成后,就可以气象站工作

public class Client {
    public static void main(String[] args) {
        // 观察者模式下,模拟气象站新增去除订阅者
        
        // 先注册一个数据源(被观察者)
        WeatherData weatherData = new WeatherData();

        // 创建一个数据源使用方(观察者)
        XiaoMiObserver xiaoMiObserver = new XiaoMiObserver();

        // 调用方注册到数据源
        weatherData.registerObserver(xiaoMiObserver);
        // 更新数据,会通知到小米(目前只有小米)
        System.out.println("信息更新.通知订阅方!");
        weatherData.setData(10.2,28,36);


        // 新增一个观察者,只需要建立对应的实体类实现对应方法
        HeFengObserver heFengObserver = new HeFengObserver();
        weatherData.registerObserver(heFengObserver);
        // 这时候数据更新会通知小米、和风
        System.out.println("信息更新.通知订阅方!");
        weatherData.setData(15.2,17.9,3.26);

        // 跟小米解除订阅关系 - 下次信息推送就不会有小米了
        weatherData.removeObserver(xiaoMiObserver);
        System.out.println("信息更新.通知订阅方!");
        weatherData.setData(55,45,25);
    }
}

运行结果

信息更新.通知订阅方!
[小米天气]今天的温度为10.2, 气压:28.0,湿度是36.0
信息更新.通知订阅方!
[小米天气]今天的温度为15.2, 气压:17.9,湿度是3.26
[和风天气]今天的温度为15.2, 气压:17.9, 湿度:3.26
信息更新.通知订阅方!
[和风天气]今天的温度为55.0, 气压:45.0, 湿度:25.0
2.3 扩展性

在装饰者模式下,我们需要新增一个合作厂商(观察者),只需要实现观察者接口,实现对应方法,不会对原先的调用产生影响,扩展性良好
1.新增具体观察者**(可以不建立对应成员变量,直接使用实现方法参数传来的数据进行处理)**

/**
 * 扩展实现 - 新增观察者 - 彩云天气
 */
public class CaiYunObserver implements Observer {
    @Override
    public void update(double temperature, double presure, double humidity) {
        System.out.println(String.format("[彩云天气]今天的温度为:%s, 气压: %s, 湿度:%s",temperature,presure, humidity));
    }
}

2.调用

        // 在原先跟小米接触合作后,接上跟彩云天气建立合作
        CaiYunObserver caiYunObserver = new CaiYunObserver();
        weatherData.registerObserver(caiYunObserver);
        System.out.println("信息更新.通知订阅方!");
        weatherData.setData(55,27,0);

结果
在这里插入图片描述

三、 框架应用

经过了解,我们知道,观察者模式应用在了Java Observable的设计中,略微不同的是,在Observable中,他没有将Subject(被观察者)抽象,而是直接在类中实现, 也是引入了一个观察者列表(obs)来维护
在这里插入图片描述
我们可以看到,对应的新增删除通知方法都有
在这里插入图片描述

我们通过读取源码确定了

  1. Observable 作为被观察者(无接口直接实现)
  2. Observer 作为观察者接口

但是Observable 在JDK9就被放弃了
因为:

  • 不可序列化-因为,Observable不实现可序列化,也不能对其子类进行序列化。
  • 没有线程安全-方法可以被其子类覆盖,并且事件通知可以以不同的顺序发生,并且可能在不同的线程上发生,这足以破坏任何“线程安全”。
  • 为了在线程之间进行可靠且有序的消息传递,请考虑使用java.util.concurrent程序包中的并发数据结构之一

四、小结

  1. 使用观察者模式设计后,会以集合的方式管理用户(Observer),包括注册,移除和通知。
  2. 这样新增观察者不需要修改气象站(被观察者)的代码,遵守了OCP原则。
  3. 我们在实现像QQ和微信公众号推送消息这种场景就可以使用观察者模式。

五、参考资料

  1. 尚硅谷Java设计模式(图解+框架源码剖析)
  2. Java InputStream层次分析
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值