观察者模式-jdk实现和自定义实现分析

设计模式-总览icon-default.png?t=LBL2https://mp.csdn.net/mp_blog/creation/editor/122202507    来自《Head First设计模式》的例子为发布订阅天气数据,当天气数据更新时自动更新面板上的天气数据。

    个人理解:观察者模式可以理解为事物之间存在一对一或者一对多的关系,当事物存在变化时需要通知其他关联事物发生变化。也可以理解成发布订阅模式,类似于消息可以进行系统解耦;只是消息可以理解成分布式的解耦(即跨进程的解耦),而该观察者模式为进程内部的发布订阅关系。

一、自己实现观察者模式

    github地址为:designpattern/src/main/java/com/kevin/designpattern/headfirst/observer/self at master · kevin-lihongmin/designpattern · GitHub

1、发布端

1)、定义主题(一些动作接口:订阅、取消订阅、变更通知)

/**
 *   观察者模式的订阅接口
 *
 * @author lihongmin
 * @date 2018/9/1 14:45
 */
public interface Subject {

    /**
     *  订阅(注册观察)
     *
     * @param observer  观察者对象
     * @return 是否订阅成功
     */
    boolean registerObserver(Observer observer);

    /**
     *  退订(取消观察)
     *
     * @param observer  观察者对象
     * @return 是否取消成功
     */
    boolean removeObserver(Observer observer);

    /**
     *  观察变更的通知接口
     */
    void notifyObservers();
}

2)、主题的实现(一个容器存放观察者列表,实现主题接口)

/**
 *   观察者模式的实现
 * @author lihongmin
 * @date 2018/9/1 15:08
 */
public class WeatherData implements Subject {

    /**
     *  所有的观察者
     */
    private ArrayList<Observer> observers;

    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData() {
        this.observers = new ArrayList<>();
        System.out.println("初始化了订阅者列表容器!");
    }

    @Override
    public boolean registerObserver(Observer observer) {
        observers.add(observer);
        System.out.println("有一个订阅者订阅了我们的变更通知!");
        return true;
    }

    @Override
    public boolean removeObserver(Observer observer) {
        if (observers.contains(observer)) {
            System.out.println("有一个订阅者取消订阅了!");
            observers.remove(observer);
            return true;
        }
        return false;
    }

    @Override
    public void notifyObservers() {
        System.out.println("有主题变更了,我要安装注册列表挨个通知他们,真麻烦!");
        // 便利,并变更通知
        for (Observer observer : observers) {
            observer.update(temperature, humidity, pressure);
        }
    }

    public void onChanged() {
        notifyObservers();
    }

    public void setChangeParam(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;

        System.out.println("收到数据变化了,先赋值再通知吧!");
        onChanged();
    }
}

2、观察者

1)、观察者定义(一个连接主题和观察者的线)

/**
 *   观察者对象定义
 *
 * @author lihongmin
 * @date 2018/9/1 14:51
 */
public interface Observer {

    /**
     *  订阅变更通知定义,当发生变更时会用参数的方式通知(下发具体实现)
     *
     * @param temperature   变更的温度
     * @param humidity      变更的湿度
     * @param pressure      变更的。。
     */
    void update(float temperature, float humidity, float pressure);
}

2)、观察到主题变更的对应动作(更新布告板天气内容)

/**
 *   收到变更需要做的事情
 *
 * @author lihongmin
 * @date 2018/9/1 14:56
 */
public interface DisplayElement {

    /**
     *  更换显示
     */
    void display();
}

3)、观察动作的实现(布告板实现)

    有了主题、有了主题订阅者的存储容器;有了观察者定义,有了观察到变更后的反映动作;那么现在需要的是将观察者和动作进行关联,所以该类实现了两个接口。

/**
 *   布告板实现
 *
 * @author lihongmin
 * @date 2018/9/1 15:22
 */
public class CurrentConditionsDisplay implements Observer, DisplayElement {

    private float temperature;

    private float humidity;

    private Subject subject;

    public CurrentConditionsDisplay(Subject subject) {
        // 初始化
        this.subject = subject;
        // 将自己注册到观察者通知列表
        System.out.println("我将自己注册到观察者通知列表!");
        subject.registerObserver(this);
    }

    @Override
    public void display() {

        System.out.println("现在变更布告板了, 现在的温度为: " + temperature + 
            " ,现在的湿度为:" + humidity);
    }

    @Override
    public void update(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        System.out.println("哈哈,订阅了那么久,我收到变更通知了!");
        display();
    }
}

3、模拟发布订阅

public class TestObserver {

    public static void main(String[] args) {
        System.out.println("初始化了天气实现!");
        WeatherData weatherData = new WeatherData();

        System.out.println("初始化了需要订阅的其中一个气象显示板(我只是其中之一)!");
        CurrentConditionsDisplay display = new CurrentConditionsDisplay(weatherData);

        System.out.println(" 气象变更了");
        weatherData.setChangeParam(20.5F, 10.5F, 24);

    }
}
初始化了天气实现!
初始化了订阅者列表容器!
初始化了需要订阅的其中一个气象显示板(我只是其中之一)!
我将自己注册到观察者通知列表!
有一个订阅者订阅了我们的变更通知!
气象变更了
收到数据变化了,先赋值再通知吧!
有主题变更了,我要安装注册列表挨个通知他们,真麻烦!
哈哈,订阅了那么久,我收到变更通知了!
现在变更布告板了, 现在的温度为: 20.5 ,现在的湿度为:10.5

分析:看似很简单的几个类,但是却感觉关系非常的复杂。有一个主题,有一个(或多个)观察者,那么肯定需要一个地方存储他们的关系(谁或者哪几个订阅了一个什么主题,谁有订阅了另一个什么主题),当然期间可以进行解绑;主题变更了怎么让观察者知道,肯定需要预留一个调用的方法;这个方法会触发什么后续订阅者的动作,定义一个接口。最后还有一个地方,让预留接口和后续动作进行关联,关联的只是接口,最终实现真正需要做的事。

后续画一个时序图吧,,,

二、Jdk的观察者模式

    github地址为:https://github.com/kevin-lihongmin/designpattern/tree/master/src/main/java/com/kevin/designpattern/headfirst/observer/jdk,知道了上面自己完全的写一个观察者模式的发布订阅流程,发现还是需要有很多的对象和关联动作,jdk对这些进行了封装,提供了主题和订阅者两个接口。

Observable(可观察的,那不就相当于是主题):

使用 Vector容器来保存订阅者,保证了并发线程的安全。

public class Observable {
    private boolean changed = false;
    private Vector<Observer> obs;
    // ......
}

Observer(观察者):与上面的一致,只需要定义变更的预留接口,回调时会告知主题和主题的订阅者列表

public interface Observer {
    /**
     * This method is called whenever the observed object is changed. An
     * application calls an <tt>Observable</tt> object's
     * <code>notifyObservers</code> method to have all the object's
     * observers notified of the change.
     *
     * @param   o     the observable object.
     * @param   arg   an argument passed to the <code>notifyObservers</code>
     *                 method.
     */
    void update(Observable o, Object arg);
}

1、主题定义

public class WeatherData extends Observable {

    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData() {
    }

    public WeatherData(float temperature, float humidity) {
        System.out.println("数据变化,设置了值,可以让观察者以拉取的方式获取数据哦!");
        this.temperature = temperature;
        this.humidity = humidity;
    }

    /**
     *  变更时调用父类方法
     *  1、{@link Observable#setChanged()} 修改状态
     *  2、{@link Observable#notifyObservers()} 采用拉取的方式
     */
    public void onChanged() {
        // 调用父类Observable的方法
        System.out.println("将Observable的变更标示更改了,并通知观察者来拉取数据了!");
        setChanged();
        notifyObservers();
    }

    /**
     *  为观察者提供拉取{@link WeatherData#temperature}的方法
     *
     * @return 温度数据
     */
    public float getTemperature() {
        System.out.println("观察者拉取了温度数据!");
        return temperature;
    }

    /**
     *  为观察者提供拉取{@link WeatherData#humidity}的方法
     *
     * @return
     */
    public float getHumidity() {
        System.out.println("观察者拉取了湿度数据!");
        return humidity;
    }

    /**
     *  为观察者提供拉取{@link WeatherData#pressure}的方法
     *
     * @return
     */
    public float getPressure() {
        return pressure;
    }
}

2、主题变更后的接连动作

/**
 *   收到变更需要做的事情
 *
 * @author lihongmin
 * @date 2018/9/1 14:56
 */
public interface DisplayElement {

    /**
     *  更换显示
     */
    void display();
}

3、主题订阅的实现

/**
 *
 * @author lihongmin
 * @date 2018/9/1 16:13
 *
 * @see Observer
 * @see Observable
 */
public class CurrentConditionsDisplay implements Observer,DisplayElement {

    Observable observable;

    private float temperature;
    private float humidity;

    public CurrentConditionsDisplay(Observable observable) {
        this.observable = observable;
        // 将自己进行注册
        observable.addObserver(this);
    }

    @Override
    public void display() {
        System.out.println("现在变更布告板了, 现在的温度为: " + temperature + 
            " ,现在的湿度为:" + humidity);
    }

    @Override
    public void update(Observable obs, Object arg) {
        System.out.println("收到数据变更了!");
        if (obs instanceof WeatherData) {
            System.out.println("原来是我订阅的WeatherData数据,我还订阅了很多种类型的数据哦!");
            WeatherData weatherData = WeatherData.class.cast(obs);
            this.temperature = weatherData.getTemperature();
            this.humidity = weatherData.getHumidity();

            System.out.println("拿到数据了,通知变更显示了!");
            display();
        }
    }
}

4、演示发布订阅流程

public class TestJdkObserver {
    public static void main(String[] args) {
        System.out.println("主题变更了,我需要new一个对象 (设置变更的温度湿度等...)! ");
        WeatherData weatherData = new WeatherData(20.5F, 10.5F);

        CurrentConditionsDisplay display = new CurrentConditionsDisplay(weatherData);

        System.out.println("准备好了,通知变更,哈哈哈! ");
        weatherData.onChanged();
    }
}
主题变更了,我需要new一个对象 (设置变更的温度湿度等...)!
数据变化,设置了值,可以让观察者以拉取的方式获取数据哦!
准备好了,通知变更,哈哈哈!
将Observable的变更标示更改了,并通知观察者来拉取数据了!
收到数据变更了!
原来是我订阅的WeatherData数据,我还订阅了很多种类型的数据哦!
观察者拉取了温度数据!
观察者拉取了湿度数据!
拿到数据了,通知变更显示了!
现在变更布告板了, 现在的温度为: 20.5 ,现在的湿度为:10.5

总结:jdk抽取了发布订阅的观察者模式中公共的部分后,我们实现观察者模式还需要做的事,如下:

1、定义主题,继承自Observable,则一个主题会有一个Vector容器存在自己订阅的观察者,并且该主题还需要提供一个触发变更的地方,该地方需要调用jdk的Observer的update接口,告诉他变更的主题和主题的订阅者列表

2、变更后的相关动作,(当然这个接口可以不用定义,在下面直接调用实现)

3、订阅者实现类,实现Observer的update接口,让主题变更时调用update,多态的调用到实现的方法,该方法也就是真正监听变化后的后续动作

4、一切准备就绪,就需要去调用主题预留的变更方法

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值