观察者模式是一种很常用的设计模式,Android中的广播(Broadcast)就是用观察者模式设计的,再往大一步,诸如微博这种社交平台也是用的观察者模式,观察者模式亦被称作发布-订阅模式。观察者模式包含两个要素:目标对象、观察者对象。其中,当目标对象的状态发生改变时,它所依赖的观察者将立即得到通知,通知携带的数据将在消息中心得到处理。
当一个对象的状态需要被多个对象了解,以保证高度协作。
假设有一个气象监测模型,那么Observer是需要获取气象通知的观察者,Subject则是气象主题,作为被观察者。
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();
}
public class WeatherData implements Subject {
private ArrayList observers;
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
this.observers = new ArrayList();
}
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
public void removeObserver(Observer o) {
int i = observers.indexOf(o);
if (i >= 0) {
observers.remove(i);
}
}
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();
}
}
除了实现Subject接口的三方法外,WeatherData中可以有自己特定的方法,用以实现具体的功能,如setMeasurements(),用以改变观察值。
在实际运用中,当气象状态信息真实变化,或状态需被按时发布时,这个方法将被调用。
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
public CurrentConditionsDisplay() {
}
@Override
public void update(float temp, float humidity, float pressure) {
this.temperature = temp;
this.humidity = humidity;
display();
}
@Override
public void display() {
Log.d("display", "Current conditions:" + temperature + "F degrees and" + humidity + "% humidity");
}
}
这里的观察者类型是CurrentConditionsDisplay,实现了Observer接口的update()方法,和DisplayElement的display()方法。
从WeatherData类中可以看到,update()方法在WeatherData类型的对象的数据改变时被调用,作为观察者的CurrentConditionsDisplay类型对象将获得最新的数据,接着调用display()展示数据。但观察者能获取主题所发布的新数据的前提是,观察者绑定了主题,且未被主题删除。
public class Test {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay display = new CurrentConditionsDisplay();
weatherData.registerObserver(display);
weatherData.setMeasurements(80, 65, 30.4f);
//weatherData.removeObserver(display);
}
}
主通过registerObserver()方法,主题注册了观察者,这一步即是订阅。
当主题调用setMeasurements()方法,就会通知观察者最新消息,这一步即是发布。
实现java.util.Observable接口,然后调用两个方法:
先调用setChanged(),标记状态已经改变;
然后调用notifyObservers()方法或notifyObservers(Object arg)方法,通知观察者,如果调用notifyObservers(Object arg)方法,其参数会被传送给观察者。
public 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 = pressure;
this.pressure = pressure;
measurementsChanged();
}
public float getTemperature() {
return temperature;
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
}
观察者实现:
public class CurrentConditionsDisplay implements Observer, DisplayElement {
private float temperature;
private float humidity;
public CurrentConditionsDisplay() {
}
@Override
public void display() {
Log.d("display", "temperature:" + temperature + ",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 Test {
public static void main(String[] args){
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay display = new CurrentConditionsDisplay();
weatherData.addObserver(display);
weatherData.setMeasurements(80, 65, 30.4f);
//weatherData.deleteObserver(display); //删除观察者
}
}
不过是方法名变了。