- 我们现在要写一个气象监测的应用,它的需求如下:
只要当天气发生变化的时候,就要更新三个布告板的显示,分别是目前天气状况(温度、湿度和气压)、气象统计(平均值等)和天气预报三个布告板。布告板要可扩展,未来可能会有新的布告板
加入进来。
有关天气的原始数据我们可以通过提供好的WeatherData对象获得,它提供了几个方法,getTemperature()、getHumidity()、getPressure()用于获得当前的温度、湿度和气压,还有一个
measurementsChanged()方法,一旦气象数据更新时,此方法就会自动被调用。
2. 我们可以通过在measurementsChanged()方法内写入布告板显示的代码,比如先加入一个目前天气状况的布告板显示更新,这样天气发生变化时,随着measurementsChanged方法被调用,我们的代
码就执行了,布告板也就更新了:
public void measurementsChanged(){ float temp = getTemperature(); float humidity= getHumidity(); float pressure= getPressure(); //目前天气状况的布告板显示更新 currentConditionssDisplay.update(temp,humidity,pressure); }
但是这样代码耦合在一起了,如果以后需要加入新的布告板,只能修改当前代码。因此我们就可以采用观察者模式。
3. 观察者模式:
定义了对象之间一对多的依赖。当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
可以理解为微博的关注,一个微博大V可以被很多人关注,当他发动态时就会通知到所有的粉丝。这里的微博大V就是一个主题,而关注他的粉丝就是观察者,当主题变化的时候,观察者会收到通知并执
行下一步动作。当然观察者同时也可以是一个主题,反之亦然。
在上面气象监测的应用中,我们也可以采用观察者模式,将WeatherData当成一个主题,将布告板当成观察者,让布告板订阅WeatherData这个主题,这样一旦天气发生变化,所有订阅了的布告板就都会
收到通知,就可以执行更新操作了。通过观察者模式我们可以实现将观察者和主题解耦,观察者可以随时订阅和取消订阅对应的主题,而不用影响主题。
上面说了观察者模式的适用场景和好处,具体的实现可以参照下图:
因此我们可以让WeatherData实现主题接口,让三个布告板实现观察者接口,并通过主题提供的注册方法让观察者都订阅WeatherData。
首先新建一个主题接口:
public interface Subject { void registerObserver(Observer observer); void removeObserver(Observer observer); void notifyObserver(); }
新建观察者接口:
public interface Observer { void update(float temp,float humidity,float pressure); }
新建一个显示接口用于布告板的显示:
public interface DisplayElement { //当布告板需要显示时,实现此方法 void display(); }
WeatherData实现Subject接口,放一个一个ArrayList类来存放观察者:
public class WeatherData implements Subject { //定义温度、适度和气压 private float temp; private float humidity; private float pressure; //创建一个ArrayList用于存放订阅此主题的观察者,通过构造方法初始化 private ArrayList<Observer> observers; public WeatherData(){ observers = new ArrayList<>(); } //将观察者注册到主题,即添加到ArrayList中 @Override public void registerObserver(Observer observer) { observers.add(observer); } //将观察者移除,即从ArrayList中remove @Override public void removeObserver(Observer observer) { //先判断下observer是否存在,不存在会返回-1 int i = observers.indexOf(observer); if (i >= 0) { observers.remove(i); } } //通知所有观察者,即调用所有观察和的update()方法,,将更新的温度、湿度、气压传进去 @Override public void notifyObserver() { for (Observer observer : observers) { observer.update(temp, humidity, pressure); } } //天气变化时自动调用的方法 public void measurementsChanged(){ notifyObserver(); } //模拟自动获得气象参数更新的方法,通过set方法更新参数,并调用measurementsChanged()方法 public void setParameters(float temp,float humidity,float pressure){ this.temp = temp; this.humidity = humidity; this.pressure = pressure; measurementsChanged(); } }
新建一个显示当前天气状况的布告板CurrentConditionsDisplay ,实现Observer接口,另外实现DisplayElement接口,重写display()方法显示布告板内容。
public class CurrentConditionsDisplay implements Observer,DisplayElement{ //定义温度、湿度和气压 private float temp; private float humidity; //保存主题是为了让以后移除观察者时更加方便 private WeatherData weatherData; //在构造方法中直接完成观察者对主题的注册 public CurrentConditionsDisplay(WeatherData weatherData) { this.weatherData = weatherData; weatherData.registerObserver(this); } @Override public void update(float temp, float humidity, float pressure) { this.temp = temp; this.humidity = humidity; display(); } //显示当时的温度和气压 @Override public void display() { System.out.println("Current Conditions:" + temp + "F degrees and" + humidity + "% humidity"); } }
上面在构造方法中注册了主题,并保存了WeatherData对象,书中说是为了以后移除观察者的时候方便。
下面测试一下:
public class Test02 { public static void main(String[] args) { WeatherData weatherData = new WeatherData(); //新建布告板,构造方法中会将观察者注册到weatherData CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData); //模拟天气状况发生改变
weatherData.setParameters(80,65,30.4f); weatherData.setParameters(78,63,29.4f); weatherData.setParameters(76,61,28.4f); } }
输出结果:
可以看到,每次天气发生变化,主题都会通知到观察者做出更新。当我们有新的观察者时,只需要实现Observer并注册到WeatherData,就会自动接到通知了。
4. 其实Java内置了观察者模式,在上面的例子中,我们发现,观察者只能被动的接受通知,不能主动获取主题的状态。而在Java内置的Observer模式既支持被动接收,也支持主动拉取。
Java内置的观察者模式为java.util包下的Observable类和Observer接口,Observable类就相当于一个主题,可被观察的,Observer就是观察者。不同之处在于Observable是一个类而不是接口,而且有
一个changed标志位,用来标志当前状态是否改变,如果为true,就通知管观察者,并提供了setChanged()来将changed置为true,可通过clearChanged()清除改变状态(将changed置为false,hasChanged()
返回changed的当前值。通过changed标志可以更灵活的控制通知,比如想在某种情况下再通知而不是已有改变就进行通知。
此外Observable中同样提供了添加、删除和通知Observer的方法。Observer接口则只有一个update(Observable o, Object arg)方法,参数中Observable 用来表示是哪个主题,Object为传递的数据对象。
除此之外,Observable类中的notifyObservers()方法是倒序通知观察者的,因此在使用观察者模式的时候不能太依赖于观察者被通知的次序,如果这样的话,一旦实现方式有所改变,通知次序就会改变,
耦合度就变高了。
5. 总结:
1)观察者模式适用于一对多的关系中,一个对象的状态发生改变,可以通知到多个依赖它的对象进行更新。
2)java自带的观察者模式的主题即可观察者Observable是一个类而不是接口,限制了它的使用和复用,因为类是单继承而接口可以多继承。因此如果有需要的话我们可以自己写主题接口。