天气预报需求

具体要求如下:

  • 1)气象站可以将每天测量到的温度,湿度,气压等等以公告的形式发布出去(比如发布到自己的网站或第三方)
  • 2)需要设计开放型 API,便于其他第三方也能接入气象站获取数据
  • 3)提供温度、气压和湿度的接口
  • 4)测量数据更新时,要能实时的通知给第三方

天气预报需求方案之普通方案

WeatherData类

通过对气象站项目的分析,我们可以初步设计出一个WeatherData

  • 1)通过getXxx方法,可以让第三方接入,并得到相关信息
  • 2)当数据有更新时,气象站通过调用dataChange()去更新数据,当第三方再次获取时,就能得到最新数据,当然也可以推送

CurrentConditions(当前的天气情况)可以理解成是我们气象局的网站

核心代码

气象网站类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* 当前的天气情况:可以理解成是气象局的网站
*/
public class CurrentConditions {
private Float temperature;
private Float pressure;
private Float humidity;

/**
* 更新天气情况,通过推送的方式,由 WeatherData 调用
*
* @param temperature
* @param pressure
* @param humidity
*/
public void update(Float temperature, Float pressure, Float humidity) {
// 更新最新天气数据
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
// 展示最新天气数据
display();
}

/**
* 公告板展示天气情况
*/
public void display() {
System.out.println("============最新天气============");
System.out.println("*** 当前温度:" + this.temperature + " ℃ ***");
System.out.println("*** 当前气压:" + this.pressure + " kPa ***");
System.out.println("*** 当前湿度:" + this.humidity + " %RH ***");
}
}

气象数据类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
/**
* 核心类
* 1、包含最新的天气信息情况
* 2、含有 CurrentConditions 对象
* 3、当数据更新时,主动调用 CurrentConditions 的 update() 方法
*/
public class WeatherData {
private Float temperature;
private Float pressure;
private Float humidity;
private CurrentConditions conditions;

/**
* 传入 CurrentConditions 对象
*
* @param conditions
*/
public WeatherData(CurrentConditions conditions) {
this.conditions = conditions;
}

public Float getTemperature() {
return temperature;
}

public Float getPressure() {
return pressure;
}

public Float getHumidity() {
return humidity;
}

/**
* 推送天气数据到网站
*/
public void dataChange() {
conditions.update(getTemperature(), getPressure(), getHumidity());
}

/**
* 当天气数据发生变化时进行更新
*
* @param temperature
* @param pressure
* @param humidity
*/
public void setData(Float temperature, Float pressure, Float humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
dataChange();
}
}

客户端调用

1
2
3
4
5
6
7
8
// 创建气象网站对象
CurrentConditions currentConditions = new CurrentConditions();
// 创建气象数据对象,并传入气象网站对象
WeatherData weatherData = new WeatherData(currentConditions);
// 天气发生变化时,更新最新的气象数据
weatherData.setData(10f, 150f, 40f);
//weatherData.setData(15f, 130f, 60f);
//weatherData.setData(13f, 160f, 20f);

测试结果

1
2
3
4
5
6
7
8
9
10
11
12
============最新天气============
*** 当前温度:10.0 ℃ ***
*** 当前气压:150.0 kPa ***
*** 当前湿度:40.0 %RH ***
============最新天气============
*** 当前温度:15.0 ℃ ***
*** 当前气压:130.0 kPa ***
*** 当前湿度:60.0 %RH ***
============最新天气============
*** 当前温度:13.0 ℃ ***
*** 当前气压:160.0 kPa ***
*** 当前湿度:20.0 %RH ***

问题分析

  • 1)其他第三方接入气象站获取数据的问题
  • 2)无法在运行时动态的添加第三方(新浪网站)
  • 3)违反OCP原则 => 观察者模式

WeatherData中增加第三方时,都需要创建对应的第三方公台板对象并加入到dataChange()方法中,既不是动态加入,也不利于维护

观察者模式原理

观察者模式类似订牛奶业务

  • 1)奶站 / 气象局:Subject
  • 2)用户 / 第三方网站:Observer

Subject:登记注册、移除和通知

  • 1)registerObserver():注册
  • 2)removeObserver():移除
  • 3)notifyObservers():通知所有的注册的用户,根据不同需求,可以是更新数据,让用户来取,也可能是实施推送,看具体需求定

Observer:接收输入

观察者模式:对象之间多对一依赖的一种设计方案,被依赖的对象为Subject,依赖的对象为ObserverSubject通知Observer变化,比如这里的奶站是Subject,是1的一方。用户是Observer,是多的一方

天气预报需求方案之观察者模式

UML 类图

核心代码

观察者对象Observer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/**
* 观察者接口
*/
public interface Observer {
void update(Float temperature, Float pressure, Float humidity);
}
/**
* 观察者实现
*/
public class CurrentConditions implements Observer {
private Float temperature;
private Float pressure;
private Float humidity;

@Override
public void update(Float temperature, Float pressure, Float humidity) {
// 更新最新天气数据
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
// 展示最新天气数据
display();
}

/**
* 公告板展示天气情况
*/
public void display() {
System.out.println("============最新天气============");
System.out.println("*** 当前温度:" + this.temperature + " ℃ ***");
System.out.println("*** 当前气压:" + this.pressure + " kPa ***");
System.out.println("*** 当前湿度:" + this.humidity + " %RH ***");
}
}

主体对象Subject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
/**
* 主体对象接口
*/
public interface Subject {
void registerObserver(Observer o);

void removeObserver(Observer o);

void notifyObservers();
}
/**
* 主体对象实现
*/
public class WeatherData implements Subject {
private Float temperature;
private Float pressure;
private Float humidity;
private List<Observer> observerList;

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

public Float getTemperature() {
return temperature;
}

public Float getPressure() {
return pressure;
}

public Float getHumidity() {
return humidity;
}

/**
* 推送天气数据到网站
*/
public void dataChange() {
notifyObservers();
}

/**
* 当天气数据发生变化时进行更新
*
* @param temperature
* @param pressure
* @param humidity
*/
public void setData(Float temperature, Float pressure, Float humidity) {
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
dataChange();
}

@Override
public void registerObserver(Observer o) {
observerList.add(o);
}

@Override
public void removeObserver(Observer o) {
if(o!= null && observerList.contains(o)) {
observerList.remove(o);
}
}

@Override
public void notifyObservers() {
for (Observer observer : observerList) {
observer.update(temperature, pressure, humidity);
}
}
}

观察者对象Observer

1
2
3
public interface Observer {
void update(Float temperature, Float pressure, Float humidity);
}

调用测试

1
2
3
4
5
6
7
8
9
10
11
12
// 创建气象网站对象
CurrentConditions currentConditions = new CurrentConditions();
// 创建气象数据对象
WeatherData weatherData = new WeatherData();
// 注册气象网站对象
weatherData.registerObserver(currentConditions);
// 天气发生变化时,更新最新的气象数据
weatherData.setData(10f, 150f, 40f);
//============最新天气============
//*** 当前温度:10.0 ℃ ***
//*** 当前气压:150.0 kPa ***
//*** 当前湿度:40.0 %RH ***

观察者模式的好处

  • 1)观察者模式设计后,会以集合的方式来管理用户Observer,包括注册、移除和通知
  • 2)这样,我们增加观察者(这里可以理解成一个新的公告板),就不需要去修改核心类WeatherData不会修改代码,遵守了ocp原则

例如,我们新增SinaWebSiteBaiDuWebSite两个三方网站,接口气象局。此时三方只需实现相应接口即可,WeatherData不需要有任何的改变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/**
* 新增的三方观察者对象——新浪网
*/
public class SinaWebSite implements Observer {
private Float temperature;
private Float pressure;
private Float humidity;

/**
* 更新天气情况,通过推送的方式,由 WeatherData 调用
*
* @param temperature
* @param pressure
* @param humidity
*/
@Override
public void update(Float temperature, Float pressure, Float humidity) {
// 更新最新天气数据
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
// 展示最新天气数据
display();
}

/**
* 公告板展示天气情况
*/
public void display() {
System.out.println("============新浪网-最新天气============");
System.out.println("*** 新浪网-当前温度:" + this.temperature + " ℃ ***");
System.out.println("*** 新浪网-当前气压:" + this.pressure + " kPa ***");
System.out.println("*** 新浪网-当前湿度:" + this.humidity + " %RH ***");
}
}
/**
* 新增的三方观察者对象——百度网
*/
public class BaiDuWebSite implements Observer {
private Float temperature;
private Float pressure;
private Float humidity;

/**
* 更新天气情况,通过推送的方式,由 WeatherData 调用
*
* @param temperature
* @param pressure
* @param humidity
*/
@Override
public void update(Float temperature, Float pressure, Float humidity) {
// 更新最新天气数据
this.temperature = temperature;
this.pressure = pressure;
this.humidity = humidity;
// 展示最新天气数据
display();
}

/**
* 公告板展示天气情况
*/
public void display() {
System.out.println("============百度网-最新天气============");
System.out.println("*** 百度网-当前温度:" + this.temperature + " ℃ ***");
System.out.println("*** 百度网-当前气压:" + this.pressure + " kPa ***");
System.out.println("*** 百度网-当前湿度:" + this.humidity + " %RH ***");
}
}

调用测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 新增三方气象网站,只需注册即可
weatherData.registerObserver(new SinaWebSite());
weatherData.registerObserver(new BaiDuWebSite());
// 天气发生变化时,更新最新的气象数据
weatherData.setData(15f, 120f, 80f);
//============最新天气============
//*** 当前温度:15.0 ℃ ***
//*** 当前气压:120.0 kPa ***
//*** 当前湿度:80.0 %RH ***
//============新浪网-最新天气============
//*** 新浪网-当前温度:15.0 ℃ ***
//*** 新浪网-当前气压:120.0 kPa ***
//*** 新浪网-当前湿度:80.0 %RH ***
//============百度网-最新天气============
//*** 百度网-当前温度:15.0 ℃ ***
//*** 百度网-当前气压:120.0 kPa ***
//*** 百度网-当前湿度:80.0 %RH ***

当三方网站不再需要时,只要做相应的移除即可

1
2
3
4
5
6
7
8
9
10
11
// 移除气象网站
weatherData.removeObserver(currentConditions);
weatherData.setData(20f, 160f, 30f);
//============新浪网-最新天气============
//*** 新浪网-当前温度:20.0 ℃ ***
//*** 新浪网-当前气压:160.0 kPa ***
//*** 新浪网-当前湿度:30.0 %RH ***
//============百度网-最新天气============
//*** 百度网-当前温度:20.0 ℃ ***
//*** 百度网-当前气压:160.0 kPa ***
//*** 百度网-当前湿度:30.0 %RH ***

JDK 源码分析

JDK 中的Observable就使用到了观察者模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Observable {
private boolean changed = false;
private Vector<Observer> obs = new Vector();

public Observable() {
}

public synchronized void addObserver(Observer var1) {
if (var1 == null) {
throw new NullPointerException();
} else {
if (!this.obs.contains(var1)) {
this.obs.addElement(var1);
}

}
}

public synchronized void deleteObserver(Observer var1) {
this.obs.removeElement(var1);
}

public void notifyObservers() {
this.notifyObservers((Object)null);
}
//...
}

public interface Observer {
void update(Observable o, Object arg);
}

角色分析和职责说明

  • 1)Observable即充当了Subject接口及其实现类,其中包括了Observer的集合,并且包括对观察者相关的注册、移除和通知等方法:addObserver()deleteObserver()notifyObservers(),这些方法就类似于我们上面例子中的registerObserver()removeObserver()notifyObservers()
  • 2)Observer即观察者接口,具有update()方法