文章目录
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等地方