Base Station

观察者模式(Observer Pettern)

"Head First学习笔记(二)"

字数统计: 1.8k阅读时长: 7 min
2017/02/07
loading

在对象之间定义一对多的依赖,这样一来,当一个对象改变状态,依赖它的对象都会收到通知,并自动更新

代码实现

观察者模式应该是JavaAndroid中用到的非常多的一个设计模式了,Java还提供了Observer相关的实现类,它们在java.util.*下,但它的Observable是个实现类,我们使用时需要继承它,这样并不方便我们的使用, 今天我们就参照书籍和相关类自己实现一个传统的观察者模式

上一次我们公司通过策略模式(StrategyPattern)来实现了一个暂时完美的产品“Bird”。公司已经步入了正轨,名气也逐渐大了起来,气象站的老板Just找到我们想让我们帮它做一款气象站的应用。

应用的功能是三块布告板,一块用来显示当天的temperature(温度)humidity(湿度)pressure(气压),另一块用来显示天气预报,最后一块用来显示当前温度的酷热指数(就是天气爽不爽)。当然这只是目前的功能,以后可能会随时增加或移除公告板。

设计程序

观察者模式有两个主要的角色 :可观察的对象observable或者也叫做Subject,订阅者observer。当observable发生改变的时候(这里对应的就是天气发生了变化),它会通知所有的observer(布告板),让它们根据情况去做自己的事情(更新显示的数据)。

气象站的功能和观察者模式非常吻合,小明决定直接参照Java中的Observer来实现:

记得上一次提起的设计原则——针对接口编程,而不是针对实现编程.我们将observerobservable的功能都用接口来声明:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//observable
public interface Observable {

void registerObserver(Observer observer);

void removeObserver(Observer observer);

void notifyObserver(Object arg);
}

//observer
public interface Observer {
void update(Observable observable,Object arg);
}

这样我们就将observable的注册、注销、和推送与Observer的更新都给抽离出来了减少了耦合增加了扩展性。

还有一点需要注意的就是我们的observer都一个通用的功能——投影到公告板,这一点在设计原则中也提到了——找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起。我们也将这个功能抽离出来。

1
2
3
public interface DisplayElement {
void display();
}

程序设计完了,来看看 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
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
public class WeatherData implements Observable {
private float temperature;
private float humidity;
private float pressure;
private List<Observer> observers;
private boolean changed;

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

public void setWeatherData(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}

public float getTemperature() {
return temperature;
}

public float getHumidity() {
return humidity;
}

public float getPressure() {
return pressure;
}

public void setChanged(boolean changed) {
this.changed = changed;
}

public void clearChanged() {
this.changed = false;
}

public void measurementsChanged() {
System.err.println("数据发生了改变");
notifyObserver(null);
}

@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}

@Override
public void removeObserver(Observer observer) {
int i = observers.indexOf(observer);
if (i >= 0) {
observers.remove(i);
}
}

@Override
public void notifyObserver(Object arg) {
if (!changed) return;
for (Observer observer : observers) {
observer.update(this, arg);
}
clearChanged();
}
}

我们在气象站通过一个List来管理订阅者们,当数据发生改变时候去通知它们,可以看到完全是面向对象的方式实现的,observable并不知道也不需要知道它的observer有哪些功能。至于为什么加一个开关changed,是因为这样我们可以更好的控制布告板的显示,我们不能每次发生一点改动就去更新它,那样用户的设备会爆炸的(当然没那么严重),有了这个”开关”我们就可以控制它的频率,比如每隔一段时间发送一次,或者变动较大发送一次。

天气数据布告板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private Observable observable;

public CurrentConditionsDisplay(Observable observable) {
this.observable = observable;
observable.registerObserver(this);
}

@Override
public void update(Observable observable, Object arg) {
this.observable = observable;
display();
}

@Override
public void display() {
if (observable instanceof WeatherData) {
WeatherData data = (WeatherData) observable;
System.err.println("温度" + data.getTemperature() + "湿度" + data.getHumidity() + "气压" + data.getPressure());
}
}
}

我们通过多态来获得observable的信息,然后调用dispaly()来显示布告板。

天气预报布告板

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 ForecastDisplay implements Observer, DisplayElement {
private float currentPressure = 29.92f;
private float lastPressure;
private Observable weatherData;

public ForecastDisplay(Observable weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}


public void display() {
System.out.print("Forecast: ");
if (currentPressure > lastPressure) {
System.out.println("Improving weather on the way!");
} else if (currentPressure == lastPressure) {
System.out.println("More of the same");
} else if (currentPressure < lastPressure) {
System.out.println("Watch out for cooler, rainy weather");
}
}

@Override
public void update(Observable observable, Object arg) {
if (observable instanceof WeatherData) {
lastPressure = currentPressure;
currentPressure = ((WeatherData) observable).getPressure();
display();
}
}
}

酷热指数布告板

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
public class ComfortLevelDisplay implements Observer, DisplayElement {
private Observable observable;

public ComfortLevelDisplay(Observable observable) {
this.observable = observable;
observable.registerObserver(this);
}

@Override
public void update(Observable observable, Object arg) {
this.observable = observable;
display();
}

@Override
public void display() {
if (observable instanceof WeatherData) {
double comfortLevel = getComfortLevel(((WeatherData) observable).getTemperature(), ((WeatherData) observable).getHumidity());
System.err.println("酷热指数 : " + comfortLevel);
}
}

public double getComfortLevel(float t, float rh) {
return (16.923 + (0.185212 * t) + (5.37941 * rh) - (0.100254 * t * rh)
+ (0.00941695 * (t * t)) + (0.00728898 * (rh * rh))
+ (0.000345372 * (t * t * rh)) - (0.000814971 * (t * rh * rh)) +
(0.0000102102 * (t * t * rh * rh)) - (0.000038646 * (t * t * t)) + (0.0000291583 *
(rh * rh * rh)) + (0.00000142721 * (t * t * t * rh)) +
(0.000000197483 * (t * rh * rh * rh)) - (0.0000000218429 * (t * t * t * rh * rh)) +
0.000000000843296 * (t * t * rh * rh * rh)) -
(0.0000000000481975 * (t * t * t * rh * rh * rh));
}
}

这一大坨代码就是用来计算”酷热指数的”。至于为什么这么写,你可以问气象局或者百度~。

最后我们来测试一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Main {
public static void main(String[] arg) {
WeatherData weatherData = new WeatherData();
ComfortLevelDisplay comfortLevelDisplay = new ComfortLevelDisplay(weatherData);
CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData);
ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);

weatherData.setWeatherData(13, 14, 15);
weatherData.setChanged(true);
weatherData.setWeatherData(15, 16, 16);
weatherData.setWeatherData(13, 16, 16);
}
}

我们在气象站中更新了三次数据来看看公告板是如何显示的吧 :

1
2
3
4
5
6
log: 数据发生了改变
log: 数据发生了改变
酷热指数 : 84.45697418607358
温度15.0 湿度16.0 气压16.0
Forecast: Watch out for cooler, rainy weather
log: 数据发生了改变

是不是和你预期的一样呢,观察者模式在这里就介绍完了。

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

要点

  • 观察者模式定义了对象之间一对多的关系。
  • 主题(也就是可观察者)用一个共同的接口来更新观察者。
  • 观察者和观察者之间用松耦合方式结合,可观察者不知道观察者的细节,只知道观察者实现了观察者接口。
  • 使用此模式时,你可以从被观察者处推(pus)或拉(pull)数据(推的方式被认为更”正确”)。
  • 有多个观察者时,不可以依赖特定的通知次序。
  • Java 有多种观察者模式的实现,包括了通用的java.util.Obseravble

day day up

CATALOG
  1. 1. 代码实现
    1. 1.1. 设计程序
    2. 1.2. 气象站
    3. 1.3. 天气数据布告板
    4. 1.4. 天气预报布告板
    5. 1.5. 酷热指数布告板
  2. 2. 要点