观察者设计模式
在此模式中,有两个角色,一个是主题,一个是观察者。
概念:
观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖着都会收到通知并自动更新。
场景
假设现在有一个气象监测应用,其中有三个部分:
1、气象站:通过各种感应装置获取到气象数据
2、WeatherData对象:取得数据
3、布告板:显示数据
我们要做的,就是利用WeatherData取得数据,并更新每个布告板,这种情况就很适合观察者模式。
思路:
WeatherData对象是一个主题,它的功能就是向每个布告板(也即观察者)发送它最新获取到的数据。
所以,它内部应该有个List列表,泛型是观察者接口(面向接口编程),每次它获取到数据,就去遍历这个List,然后调用观察者的更新方法。
类图
代码实现:
接口
主题接口
public interface Subject {
public void registerObserver(Observer observer);
public void removeObserver(Observer observer);
public void notifyObservers();
}
观察者接口
public interface Observer {
public void update(float temp,float humidity,float pressure);
}
展示接口
这个接口跟观察者模式没关系,只是这里的场景是个布告板,需要对数据展示,所以弄了这么一个接口
public interface DisplayElement {
public void display();
}
实现类
WeatherData
public class WeatherData implements Subject {
private java.util.List<Observer> observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherData(){
observers=new ArrayList<>();
}
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer o:observers){
o.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();
}
}
布告板
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
//这个方法是我自己添加的,与书中不同,但我认为应该有这么一个订阅的方法
public void subscribe(Subject subject){
subject.registerObserver(this);
}
//以及这个取消订阅的方法
public void cancelSubscribe(Subject subject){
subject.removeObserver(this);
}
@Override
public void display() {
System.out.println("Current conditions: "+temperature+"F degrees and "+ humidity+"% humidity");
}
@Override
public void update(float temp, float humidity, float pressure) {
this.temperature=temp;
this.humidity=humidity;
display();
}
}
测试
public static void main(String[] args) {
//主题
WeatherData weatherData=new WeatherData();
//观察者
CurrentConditionsDisplay currentConditionsDisplay=new CurrentConditionsDisplay();
//观察者去订阅气象数据(其实就是让主题的list加上当前观察者)
currentConditionsDisplay.subscribe(weatherData);
//当它拿到数据,就遍历list中的每个观察者,分别调用这些观察者的update方法
weatherData.setMeasurements(1.0f,2.0f,3.0f);
}
结果:
Java内置的观察者模式
java.util包内置了 Observer接口 和 Observable类
源码
Observer接口就观察者
public interface Observer {
void update(Observable o, Object arg);
}
Observable类就是主题
public class Observable {
private boolean changed = false;
private Vector<Observer> obs;
public Observable() {
obs = new Vector<>();
}
//订阅
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}
//取消订阅
public synchronized void deleteObserver(Observer o) {
obs.removeElement(o);
}
public void notifyObservers() {
notifyObservers(null);
}
public void notifyObservers(Object arg) {
Object[] arrLocal;
synchronized (this) {
//若没有改变,无需通知观察者
if (!changed)
return;
//这里为啥非得转成Object[]数组呢?直接遍历Vector不行吗?---大佬们请在评论区解惑
arrLocal = obs.toArray();
//把changed标识设置为false
clearChanged();
}
//逐个通知观察者
for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}
public synchronized void deleteObservers() {
obs.removeAllElements();
}
protected synchronized void setChanged() {
changed = true;
}
protected synchronized void clearChanged() {
changed = false;
}
public synchronized boolean hasChanged() {
return changed;
}
public synchronized int countObservers() {
return obs.size();
}
}
上面的代码中,这个changed是干嘛用的呢?
书中解释如此:
比如气象站测量比较敏锐,温度计读数每十分之一度就会更新,这会造成WeatherData对象不断地通知观察者,我们可能觉得这样太频繁了,只希望半度以上才更新,那么就可以在温度差距达到半度时,调用setChanged()方法,达到有效的更新。
那我们现在要做的,是用Java里面写好的观察者模式,去重写改造上面的气象站
利用内置的支持重做气象站
改造后代码如下
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
public void subscribe(Observable subject){
subject.addObserver(this);
}
public void cancelSubscribe(Observable subject){
subject.deleteObserver(this);
}
@Override
public void display() {
System.out.println("Current conditions: "+temperature+"F degrees and "+ humidity+"% humidity");
}
@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();
}
}
}
public class WeatherData extends Observable {
private float temperature;
private float humidity;
private float pressure;
public void measurementsChanged(){
//要先调用setChanged表示状态已改变
setChanged();
//这里实际上调用notifyObservers(null),没有传递数据,所以是观察者来拉取我们的数据
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 float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
}
另外,在Swing中许多地方也用到了观察者模式,比如JButton