1、角色说明
观察者模式中,主要有两类角色:主题(subject)和观察者(observer)。
- 主题:采集数据,将更新消息推送给订阅该主题的所有观察者;
- 观察者: 订阅了主题之后,可以从主题中接收通知;
此模式结构类图如下:
2、实例问题
java中内置有观察者模式,但是为了更好地理解此模式,我们以《Head First 设计模式》
一书中的例子为切入点,自己搭建一套关于天气的观察者模式Demo。
问题描述如下:
- WeatherData获得最新的测量数据(温度(temprature)、湿度(humidity)、气压(pressure);
- CurrentConditionDisplayer
实时更新
显示当前最新天气数据;
为解决上述问题,我们建立如下所示结构:
其中:
- ISubject:主题接口,定义主题所需基本行为–观察者订阅入口(registerObserver)、观察者解除订阅入口(removeObserver)、通知所有已订阅观察者方法(notifyObservers);
- IObserver:观察者接口,定义所有观察者所需基本行为–接收主题推送的消息,更新自己的数据内容(update);
- IDisplayElement:展示接口,和IObserver一起规定观察者的行为准则–显示数据(display)
3、问题实践
对应提出的问题,实现上述接口的类内容如下:
天气数据主题类(WeatherData)
package design.practice.weather.data.displayer.subject;
import design.practice.weather.data.displayer.observer.IObserver;
import java.util.ArrayList;
import java.util.List;
/**
* @author yanzy
* @date 2018/11/5 上午9:29
* @description 主题接口的一个实现类--天气数据接收器
*/
public class WeatherData implements ISubject {
private List<IObserver> observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
this.observers = new ArrayList<IObserver>();
}
@Override
public void registerObserver(IObserver observer) {
observers.add(observer);
}
@Override
public void removeObserver(IObserver observer) {
if (observers.indexOf(observer) >= 0) {
this.observers.remove(observer);
}
}
@Override
public void notifyObservers() {
observers.forEach(o -> {
o.update(this.temperature, this.humidity, this.pressure);
});
}
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
notifyObservers();
}
}
此为一主题:
- 所有订阅此主题的观察这都存储在
observers
这个List当中,注册和取消订阅行为都以此为基础进行; - 当内容更新时,遍历此主题实力中的observers,逐一调用其update方法,完成消息推送。
当前天气条件展示面板(CurrentConditionDisplay)
package design.practice.weather.data.displayer.observer;
import design.practice.weather.data.displayer.subject.ISubject;
/**
* @author yanzy
* @date 2018/11/6 上午9:36
* @description 展示面板--气温和湿度
*/
public class CurrentConditionDisplay implements IObserver, IDisplayElement {
private ISubject subject;
private Float temperature;
private Float humidity;
public CurrentConditionDisplay(ISubject subject) {
this.subject = subject;
}
@Override
public void display() {
if (this.temperature == null && this.humidity == null) {
System.out.println("no message from design.practice.weather.data.displayer.subject!\n");
}
System.out.println("current conditions: -" + this.temperature + "F- degrees and -" + this.humidity + "%- humidity");
}
@Override
public void update(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.display();
}
public void subscribe() {
this.subject.registerObserver(this);
System.out.println("success to subscribe " + subject);
}
public void unsubscribe() {
this.subject.removeObserver(this);
System.out.println("success to unsubscribe " + subject);
}
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;
}
}
此为一观察者:
- 实现
IObserver
和IDisplayElement
接口,其构造函数传入了一个ISubject
接口,通过调用该接口实现类的registerObserver()
和removeObserver()
方法来实现对主题的订阅和解除订阅操作; - 关键方法为
update()
,根据传入的值来更新当前观察者中的数据内容,并进行展示。
实例测试
package design.practice.weather.data.displayer;
import design.practice.weather.data.displayer.observer.CurrentConditionDisplay;
import design.practice.weather.data.displayer.subject.WeatherData;
/**
* @author yanzy
* @date 2018/11/6 上午9:47
* @description
*/
public class Main {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
weatherData.setMeasurements(1F, 2F, 3F);
System.out.println("no design.practice.weather.data.displayer.observer subscribe design.practice.weather.data.displayer.subject now!\n");
CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(weatherData);
currentConditionDisplay.subscribe();
weatherData.setMeasurements(10F, 20F, 30F);
currentConditionDisplay.unsubscribe();
weatherData.notifyObservers();
}
}
第一次无观察者订阅主题时,更新主题内容,无观察者接收到消息;后续CurrentConditionDisplay订阅主题,更新主题内容为10、20、30,CurrentConditionDisplay接受到通知,并显示当前最新天气数据内容;
执行结果如下:
no design.practice.weather.data.displayer.observer subscribe design.practice.weather.data.displayer.subject now!
success to subscribe design.practice.weather.data.displayer.subject.WeatherData@4f3f5b24
current conditions: -10.0F- degrees and -20.0%- humidity
success to unsubscribe design.practice.weather.data.displayer.subject.WeatherData@4f3f5b24
更多代码详情,请见:
https://github.com/yzy199391/HeadFirstDesignPatternPractice/tree/master/capter2observer
4、java内置观察者模式
通过上述实例,我们对于观察者模式已经有了一定的了解;其实,对于这种常用的设计模式,java中已经提供了现成的解决方案。
其大致思路与上述实例中无异:
- 在java内置的模式中,上述实例中的主题实现类(WeatherData)通过继承Observeable类获取主题的基本行为实现。
- 实现
java.util.Observer
接口,将对象声明为一个观察者,通过调用Observable的addObserver()
方法订阅主题;通过调用deleteObserver()
方法取消订阅。- 主题发送通知:先调用
setChaged()
方法改变chaged属性状态为true;调用两种notifyObservers()
方法中的一种来逐一通知观察者–notifyObservers()/notifyObservers(Object arg);后一种方式可控制传送任何数据对象给每一个观察者。- 观察者接收通知:通过主题调用update(Observable o, Object arg)将数据推(push)给观察者,此时需要主题notify的方式为上述的第二种方式;否则,需要观察者自行从主题中拉取内容。
此处就不自行实现上述实践问题,引用《Head First 设计模式》
一书中的样例来进行展示: