定义
观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
Java内置的观察者
- Java API有内置的观察者。java.util包(package)内包含最基本的Observer接口与Observable类。
- 现在我们实现一个气象站,当天气(温度、湿度、气压)有变化的时候,我们就更新目前状况、气象统计、天气预报的显示布告板。
气象站就是我们的主题(可观察者),那它就直接继承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
32
33
34
35
36
37
38
39
40
41
42
43
44
45public class WeatherData extends Observable {
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
}
public void measurementsChanged() {
setChanged();
//该方法会通知所有订阅的观察者;未直接传送数据。 观察者想获取数据需要自行拉取。
notifyObservers();
}
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
public float getTemperature() {
return temperature;
}
public void setTemperature(float temperature) {
this.temperature = temperature;
}
public float getHumidity() {
return humidity;
}
public void setHumidity(float humidity) {
this.humidity = humidity;
}
public float getPressure() {
return pressure;
}
public void setPressure(float pressure) {
this.pressure = pressure;
}
}Observable类已经实现了
addObserver
增加,deleteObserver
删除,notifyObservers
、notifyObservers(Object arg)
通知观察者的方法。要成为观察者只需实现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//目前状况 布告板
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
public CurrentConditionsDisplay(Observable observable) {
observable.addObserver(this);
}
@Override
public void update(Observable o, Object arg) {
if(o instanceof WeatherData){
WeatherData weatherData = (WeatherData)o;
this.temperature = weatherData.getTemperature();
this.humidity = weatherData.getHumidity();
display();
}
}
@Override
public void display() {
System.out.println("Current conditions:" + temperature + "F degress and " + humidity + "% humidity");
}
}上面的主题是通过
notifyObservers()
无参方式通知观察者,所以观察者通过拉的方式获取了主题的数据,达到了拿你所需。当然我们也可以在主题中通过notifyObservers(Object arg)
方法,把数据通过推的形式传送给观察者,这样的话,可能会造成数据冗余。其他的StatisticsDisplay统计和ForecastDisplay预测布告板就不一一实现了。- Observable的黑暗面:首先,Observable是一个类,你必须设计一个类继承它,如果某类想同时具有Observable类和另一超类的行为,就会陷入两难。再者,因为没有Observable接口,所以你无法建立自己的实现,和Java内置的Observer API搭配使用。而且setChanged()方法被保护起来,这意味着除非你继承Observable,否则无法创建Observable实例并组合到你自己的对象中来。对于Observable已经实现的方法无法再做修改,例如通知观察者的顺序。
定制面向接口实现观察者模式。
a. 定义主题接口Subject,包含注册,删除,通知三个基本方法。
b. 定义观察者接口Observer,一个update方法,当主题状态改变是它被调用
c. WeatherData,一个具体主题总是实现主题接口,除了实现主题已经定义的方法,具体主题也可以添加其他的属性、方法或实现其他接口。
d. 具体的观察者可以是实现Observer接口的任意类。观察者必须注册具体主题,以便接收更新。
主题接口
1
2
3
4
5
6
7
8public interface Subject {
//注册
void registerObserver(ObserverMyself o);
//删除
void removeObserver(ObserverMyself o);
//通知所有观察者
void notifyObservers();
}观察者接口
1
2
3
4public interface ObserverMyself {
//当气象观测值改变时,主题把这些状态值当作方法当参数,传送给观察者。
void update(float temp,float humidity,float pressure);
}布告板接口
1
2
3public interface DisplayElement {
void 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
34
35
36
37
38
39
40
41
42
43
44public class WeatherData implements Subject {
private ArrayList<ObserverMyself> observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherData(){
observers = new ArrayList();
}
@Override
public void registerObserver(ObserverMyself o) {
observers.add(o);
}
@Override
public void removeObserver(ObserverMyself o) {
int i = observers.indexOf(o);
if(i>=0){
observers.remove(i);
}
}
@Override
public void notifyObservers() {
for (ObserverMyself observer : observers) {
observer.update(temperature,humidity,pressure);
}
}
public void measurementsChanged(){
notifyObservers();
}
// public void setMeasurements(float temperature,float humidity,float pressure){
// this.temperature = temperature;
// this.humidity = humidity;
// this.pressure = pressure;
// measurementsChanged();
// }
}目前状况 布告板
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public class CurrentConditionsDisplay implements ObserverMyself, DisplayElement {
private float temperature;
private float humidity;
private Subject weatherData;
public CurrentConditionsDisplay(Subject weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
@Override
public void update(float temp, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
display();
}
@Override
public void display() {
System.out.println("Current conditions:" + temperature + "F degress and " + humidity + "% humidity");
}
}
观察者保存对Subject的引用是为了防止以后可能想取消注册。该模式下实现的观察者模式使用了推的方式传送数据。
总结
对于具体应该是自己实现一个观察者还是使用jdk自带的,就需要看实际情况了。不管用哪一种方法,反正你都已经熟悉观察者模式了,应该都能善用它们。