《HF 设计模式》 C2 观察者模式

1 模拟气象站布告

在一个模拟气象站布告的应用中,我们将建立3个布告板,分别为:目前天气,气象统计和天气预报,气象站提供了一个WeatherData对象,它能够及时获取温度,湿度,气压等信息,我们根据WeatherData获取到的数据来更新这几个布告板
在这里插入图片描述

但是布告板的更新是由用户选择的,用户可以选择性的订阅这三个布告板中的一个,两个或全部,我们应该怎样更新用户选择的布告板?

1.1 一个错误示范

建立三种布告板:

/**
 * @author 雫
 * @date 2021/3/1 - 15:24
 * @function 目前天气布告板
 */
public class CurrentConditionDisplay {

    public CurrentConditionDisplay() {}

    public static void updateCurrentConditionDisplay() {
        System.out.println("更新了目前天气布告板");
    }
}


/**
 * @author 雫
 * @date 2021/3/1 - 15:26
 * @function 历史天气布告板
 */
public class HistoryConditionDisplay {

    public HistoryConditionDisplay() {}

    public static void updateHistoryConditionDisplay(){
        System.out.println("更新了历史天气布告板");
    }
}


/**
 * @author 雫
 * @date 2021/3/1 - 15:28
 * @function 天气预报布告板
 */
public class ForecastDisplay {

    public ForecastDisplay() {}

    public static void updateForecastDisplay() {
        System.out.println("更新了天气预报布告板");
    }
}

WeatherData:

/**
 * @author 雫
 * @date 2021/3/1 - 15:20
 * @function 能及时获取气象站数据的类
 */
public class WeatherData {

    public WeatherData() {}

    public void getTemperature() {
        System.out.println("获取了气象站的温度信息");
    }

    public void getHumidity() {
        System.out.println("获取了气象站的湿度信息");
    }

    public void getPressure() {
        System.out.println("获取了气象站的气压信息");
    }

    public void updateNotice() {
        getTemperature();
        getHumidity();
        getPressure();

        CurrentConditionDisplay.updateCurrentConditionDisplay();
        HistoryConditionDisplay.updateHistoryConditionDisplay();
        ForecastDisplay.updateForecastDisplay();
    }

}

对于上述的程序,WeatherData虽然能更新布告板,但是只能全部更新,用户只订阅了一个布告板或两个时,都需要重改源码才能更新自己订阅的布告板,这显然是不合理的,随着需求的改变而不断地更改源码,绝对是错误的

那我们应该怎样更新未知的布告板呢?

1.2 报社和订阅者

看看生活中报社的工作模式

订阅过程:
1,用户向报社订阅报纸
2,报社将用户加入订阅名单
3,报社将报纸寄给名单中的订阅者

退订过程:
1,用户告知报社不再订阅
2,报社将用户从订阅名单中取消
3,报社将报纸寄给名单中的订阅者

观察者模式 = 出版社 + 订阅者,只是在观察者模式中,具有报社功能的类称为主题,具有用户功能的类称为观察者
在这里插入图片描述
观察者模式可以帮你的对象知悉现状,不会错过对象感兴趣的事,对象甚至在运行时可决定是否要被继续通知,观察者模式是JDK中使用最多的模式之一,使用观察者模式,不仅能够消息灵通,还能松耦合

1.3 定义观察者模式

观察者模式:定义了对象间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会接到通知并自动更新

主题的职能:

1,添加观察者到自己的订阅列表
2,从订阅列表中移除指定观察者
3,向所有观察者发布数据

观察者的职能:

1,获取来自主题的数据并作出反应

主题是真正拥有数据的人,观察者依附主题而工作,主题在数据变化时发布更新,这样比起让许多对象共享一份数据而言,能得到更干净的设计

1.4 松耦合

当两个对象之间松耦合,它们依然可以交互,但是不太清除彼此的细节

设计原则:
为了交互对象之间的松耦合设计而努力

观察者模式不仅能完成对所有观察者发布数据,更是达成了松耦合
主题不需要知道关于观察者的详细信息,不会受到任何影响,只需要发布数据即可,一个新的类想称为观察者,不需要修改源码,只要实现一个接口,注册成观察者即可,观察者不需要知道主题的详细信息,它只在乎来自主题的数据

松耦合的设计能够让对象之间的依赖关系降到最低,以此建立有弹性,能够面对变化的系统

1.5 实现气象站布告系统

在这里插入图片描述
让WeatherData作为主题,实现Subject接口,让剩下的三个布告板作为观察者,实现Observer接口

需要的接口:

/**
 * @author 雫
 * @date 2021/3/1 - 16:43
 * @function 主题接口
 * 具有注册观察者,移除观察者,发布通知的功能
 */
public interface Speaker {

    void registerObserver(Listener listener);

    void removeObserver(Listener listener);

    void notifyObservers();
}

/**
 * @author 雫
 * @date 2021/3/1 - 16:44
 * @function 观察者接口
 * 能够得到来自主题的数据并处理
 */
public interface Listener {

    void update(float temp, float humidity, float pressure);

}

/**
 * @author 雫
 * @date 2021/3/1 - 16:46
 * @function 布告板展示信息的接口
 */
public interface DisplayElement {

    void display();
}

WeatherData:

/**
 * @author 雫
 * @date 2021/3/1 - 16:49
 * @function 模拟气象站数据收集器
 * 作为Subject,可以添加,移除观察者
 * 对所有观察者发布数据
 */
public class WeatherData implements Speaker {
    private float temp;
    private float humidity;
    private float pressure;

    private ArrayList<Listener> listeners;

    public WeatherData() {
        listeners = new ArrayList<>();
    }

    public void dataChange() {
        notifyObservers();
    }

    public void setData(float temp, float humidity, float pressure) {
        this.temp = temp;
        this.humidity = humidity;
        this.pressure = pressure;
        notifyObservers();
    }

    @Override
    public void registerObserver(Listener listener) {
        listeners.add(listener);
    }

    @Override
    public void removeObserver(Listener listener) {
        int i = listeners.indexOf(listener);
        if(i >= 0) {
            listeners.remove(listener);
        }
    }

    @Override
    public void notifyObservers() {
        for(Listener listener : listeners) {
            listener.update(temp, humidity, pressure);
        }
    }
}

公告板:

/**
 * @author 雫
 * @date 2021/3/1 - 16:47
 * @function 当前温度布告板
 */
public class CurrentConditionDisplay implements Listener, DisplayElement {
    private float temp;
    private float humidity;
    private float pressure;

    private WeatherData weatherData;

    public CurrentConditionDisplay(WeatherData weatherData) {
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }

    @Override
    public void display() {
        System.out.println("实时温度布告板:");
        System.out.println(this.temp);
        System.out.println(this.humidity);
        System.out.println(this.pressure);
    }

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

}

...

我们让这三个布告板在生成对象时需要的参数是WeatherData类对象,依次生成布告板对象时,调用WeatherData对象的registerObserver()方法,将自己作为观察者,这样就不必更改源码的情况下,对用户选择的布告板发布信息

测试:
在这里插入图片描述

当每次气象站数据变化时,都会调用dataChange()方法,而dataChange()方法又会调用notifyObserver(),以此来完成实时广播

1.6 观察者获取数据的方式

上述我们实现的观察者模式,主题将自己认为有必要的数据全部发送给了观察者,但是如果有些观察者只需要一小部分数据,那么上述的方式令观察者收到了一堆无用数据,这样的方式就有些冗余了

对于上述的方式,称为主题向观察者推送数据,每个观察者为了获得自己需要的数据,可以从主题那里拉取数据

1,主题推送数据:
将所有认为有必要的数据全部发送给观察者
实现的方式就是上述气象站布告系统的方式
WeatherData将temp,humidity,pressure发给所有观察者

2,观察者拉取数据:
观察者根据自己的需要从主题那里拉取数据
可以将主题本身发送过去,观察者根据get方法获取主题内的数据

但对比上述获取数据“推送”和“拉取”的两种方式,推送更为正确,因为它只是传递了一些数据,而拉取会调用主题的get方法,此时观察者将手伸进了主题内,有违低耦合的初衷

1.7 Java内置的观察者模式

java.util内包含最基本的Observer接口(由观察者实现)和Observable类(由主题继承),即可以完成“推送”,也可以完成“拉取”

我们用Java内置的观察者模式来设计气象布告系统
在这里插入图片描述

将某个类变为主题广播数据
1,让WeatherData继承Observable类
2,调用setChanged()方法,标记状态已经改变
3,调用notifyObservers()方法中的一个
notifyObservers() 不直接传递数据,等待观察者拉取数据
notifyObservers(Object arg) 主题给观察者推送数据
让某个类变为观察者获取数据
1,实现Observer接口
2,调用update(Observable o, Object arg)
当主题调用notifyObservers(Object arg),arg为推送数据
该方法,当主题调用notifyObservers()时,arg为空
观察者可以从o那里调用get方法来拉取数据

1.8 设立changed标志

关于主题的setChanged()方法:
setChanged()方法用来标记状态已经改变的事实,好让notifyObservers()知道它被调用时应该更新观察者

在这里插入图片描述
这样是有其必要性的,setChanged()方法可以让你再更新观察者时,有更多的弹性,可以更适当的通知观察者

比如没有setChanged()方法时,气象站测量得到的数据十分敏感,每更改0.01都要更新一次,这是没有必要的,所以可以在数据更改一定范围后,再调用setChanged()方法来通知观察者,同时有hasChanged()方法得知changed标志的当前状态

1.9 使用Java内置观察者模式

WeatherData:

/**
 * @author 雫
 * @date 2021/3/1 - 18:53
 * @function
 */
public class WeatherData extends Observable {
    private float temp;
    private float humidity;
    private float pressure;

    public WeatherData() {}

    public float getTemp() {
        return temp;
    }

    public float getHumidity() {
        return humidity;
    }

    public float getPressure() {
        return pressure;
    }

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

    public void setData(float temp, float humidity, float pressure) {
        this.temp = temp;
        this.humidity = humidity;
        this.pressure = pressure;
        dataChange();
    }

}

布告板:

/**
 * @author 雫
 * @date 2021/3/1 - 18:54
 * @function 当前气象布告
 */
public class CurrentConditionDisplay implements Observer {
    private float temp;
    private float humidity;
    private float pressure;

    private Observable observable;

    public CurrentConditionDisplay(Observable observable) {
        this.observable = observable;
        observable.addObserver(this);
    }

    @Override
    public void update(Observable o, Object arg) {
        WeatherData weatherData = (WeatherData) o;
        this.temp = weatherData.getTemp();
        this.humidity = weatherData.getHumidity();
        this.pressure = weatherData.getPressure();
        disPlay();
    }

    public void disPlay() {
        System.out.println("当前气象:" +
                this.temp + " " + this.humidity +  " " + this.pressure);
    }

}

测试:
在这里插入图片描述
上述的程序观察者获取数据采用了拉取的方式,但这样做违背了松耦合,但是采用推送的方式,一次只能推送一个数据,所以不建议使用Java内置的观察者模式,自己定义比较好

且Java内置的观察者模式还存在一些缺陷
Observable是一个类,这就是最大的缺陷,主题是一个类而不是一个接口

1,Observable是一个类,一个类必须继承它才能作为主题
但Java只能单继承,继承了Observable就限制了继承其它类,限制了使用和复用
违背了"针对接口编程"

2,Observable保护了关键的方法,如setChanged()方法是protected修饰的
想用setChanged()方法,就必须得继承
违背了"多用组合,少用继承"

建议不要为了方便使用内置的观察者模式,导致系统的弹性降低

1.10 观察者模式小结

观察者模式定义了对象间的一对多关系

观察者和主题之间采用松耦合结合,主题不知道观察者的功能,观察者不在意主题的细节

有多个观察者时,代码不可以依赖特定的通知次序

Swing大量的使用了观察者模式,许多GUI框架也是如此,且观察者模式也被应用于JavaBeans,RMI等地方

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值