读headFirst设计模式 - 观察者模式
上次学习了策略模式,这次来学习观察者模式。这次先把书上的例子学习一下,然后再自己写一个例子,看是否能做到举一反三(或者说触类旁通),不过要想真正的掌握还要多多思考和练习。
学习书上的例子
现在我们有一个任务,需要根据天气状况来发布不同的布告,开始有3个布告板:当前状况,气象统计,天气预报。像这样的:
现在有一个天气情况的类WeatherData,可以设置和获取温度temperature,湿度humidity和气压pressure数据。要在天气变化时通知布告板,布告板更新以显示不同的值。开始可能想要这样做
我们以后需要添加新的布告板或者删除布告板,这就要求系统具有弹性,如果是按照上图这样做,每添加或删除布告板都需要修改WeatherData类。好吧,既然这样,我想我们需要来使用观察者模式了。
认识观察者模式
加入用户订阅了报纸,报社只要有新的报纸就会给用户送过去,当用户不想看报纸了,就取消订阅,报社就不会送新的报纸过去了。在这里就是一个观察者模式,不过需要改一个名字:报社称为“主题”,用户称为“观察者”。只要主题有更新就会通知观察者,观察者然后更新自身状态。想想天气的例子是不是很类似,所以我们把观察者模式运用在天气的例子中,于是在我们的天气的例子中,天气状况就是主题,布告板就是观察者,只要天气状况发生改变就会通知布告板,然后布告板收到通知并改变自身状态。
定义观察者模式
在真实世界中,你通常会看到观察者模式被定义成:定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
观察者模式类图
通常观察者模式被设计成包含Subject和Observer接口。看一下类图
软件设计原则
观察者模式通过实现接口的方式实现针对接口编程(这也是一种设计原则,上次说过,这里的接口不一定是interface关键字关键字修饰的接口)建立更有弹性的系统,使对象间的互相依赖降到了最低,这就是所说的松耦合。所以这里的设计原则是:为了对象之间的松耦合而努力。
使用观察者模式来写天气的例子
观察者接口Observer:
1 /** 2 * 观察者中只有一个update方法 3 */ 4 public interface Observer { 5 void update(float temp, float humidity, float pressure); 6 }
这里update的参数是温度等一个个具体的值,这样不太好,在后面换成对象会好些。
主题接口Subject:
/** * 主题接口 */ public interface Subject { void registerObserver(Observer observer); void removeObserver(Observer observer); void notifyObservers(); }
显示布告板内容的接口DisplayElement:
/** * 显示布告板内容的接口 */ public interface DisplayElement { void display(); }
天气状况WeatherData类实现主题接口Subject,所以WeatherData就是一个主题,当天气发生改变时通知所有的观察者
具体的主题WeatherData类:
public class WeatherData implements Subject { private ArrayList<Observer> observers = new ArrayList<Observer>();//存储观察者 private float temperature; //温度 private float humidity; //湿度 private float pressure; //气压 @Override public void registerObserver(Observer observer) { observers.add(observer); } @Override public void removeObserver(Observer observer) { observers.remove(observer); } //通知所有观察者 @Override public void notifyObservers() { for (int i = 0, j = observers.size(); i < j; i++) { observers.get(i).update(temperature, humidity, pressure); } } //测量值发生改变 private void measurementsChanged() { notifyObservers(); } //设置测量值 public void setMeasurements(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; measurementsChanged(); } }
具体的观察者:当前状况CurrentConditionDisplay类
public class CurrentConditionDisplay implements Observer, DisplayElement { private float temperature; //温度 private float humidity; //湿度 private float pressure; //气压 private Subject weatherData; //创建观察者时传入主题对象,将自己注册成观察者 public CurrentConditionDisplay(Subject weatherData) { this.weatherData = weatherData; //将自己注册成观察者 weatherData.registerObserver(this); } //移除观察者 public void removeObserver() { weatherData.removeObserver(this); } @Override public void display() { System.out.println(toString()); } @Override public void update(float temp, float humidity, float pressure) { this.temperature = temp; this.humidity = humidity; this.pressure = pressure; display(); } @Override public String toString() { return "CurrentConditionDisplay [temperature=" + temperature + ", humidity=" + humidity + ", pressure=" + pressure + "]"; } }
这里怎么显示或利用这些数据可以自己设计
测试一下:
public class ObserverTest { public static void main(String[] args) { //创建一个具体的主题 WeatherData weatherData = new WeatherData(); //创建一个具体的观察者 CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(weatherData); weatherData.setMeasurements(23, 45, 1); weatherData.setMeasurements(28, 31, 1); weatherData.setMeasurements(36, 12, 1); currentConditionDisplay.removeObserver();//移除 weatherData.setMeasurements(15, 21, 1); } }
你会看到每当天气改变时布告板会收到通知并自动更新
再附加一个气象统计的类,其他的类和测试类自己写一下
/** * 获取平均值 */ public class WeatherStatistics implements Observer, DisplayElement { private static final float[] tempArr = new float[2]; //分别存贮次数和总数 private static final float[] humidityArr = new float[2]; private static final float[] pressureArr = new float[2]; private float temperature; private float humidity; private float pressure; //创建对象自动注册为观察者 public WeatherStatistics(Subject weatherData) { weatherData.registerObserver(this); } @Override public void display() { System.out.println(toString()); } @Override public void update(float temp, float humidity, float pressure) { average(temp, humidity, pressure); display(); } private void average(float temp, float humidity, float pressure) { tempArr[0] = tempArr[0] + 1; tempArr[1] = tempArr[1] + temp; humidityArr[0] = humidityArr[0] + 1; humidityArr[1] = humidityArr[1] + humidity; pressureArr[0] = pressureArr[0] + 1; pressureArr[1] = pressureArr[1] + pressure; this.temperature = tempArr[1] / tempArr[0]; this.humidity = humidityArr[1] / humidityArr[0]; this.pressure = pressureArr[1] / pressureArr[0]; } @Override public String toString() { return "WeatherStatistics [temperature=" + temperature + ", humidity=" + humidity + ", pressure=" + pressure + "]"; } }
这样我们就设计了自己的给观察者模式,其实jdk中已经内置了观察者模式,位于java.util包下,与我们自己设计的有些不同,主题是继承Observable类,这时可以称之为“可观察者”,里面也有添加删除观察者的方法。观察者实现Observer接口,可观察者要如何送出通知呢?需要下面的步骤:
推荐看一下源码,会更加清晰
现在用jdk内置的观察者模式修改一下天气的例子
WeatherData:
import java.util.Observable; /** * Observable 可观察者,即以前的主题 */ public class WeatherData extends Observable { private float temperature; private float humidity; private float pressure; public WeatherData() {} public void setMeasurements(float temperature, float humidity, float pressure) { this.temperature = temperature; this.humidity = humidity; this.pressure = pressure; measurementsChanged(); } //测量值发生改变时 private void measurementsChanged() { setChanged(); notifyObservers(); } public float getTemperature() { return temperature; } public float getHumidity() { return humidity; } public float getPressure() { return pressure; } }
CurrentConditionDisplay:
/** * 观察者,实现了观察者接口 */ public class CurrentConditionDisplay implements Observer, DisplayElement { private Observable observable; private float temperature; private float humidity; private float pressure; public CurrentConditionDisplay(Observable observable) { this.observable = observable; observable.addObserver(this);//添加为可观察者中的观察者 } @Override public void update(Observable o, Object arg) { WeatherData weatherData = (WeatherData) o; this.temperature = weatherData.getTemperature(); this.humidity = weatherData.getHumidity(); this.pressure = weatherData.getPressure(); display(); } @Override public void display() { System.out.println(toString()); } @Override public String toString() { return "CurrentConditionDisplay [temperature=" + temperature + ", humidity=" + humidity + ", pressure=" + pressure + "]"; } }
这个和我们刚才自己设计的不同:把主题(可观察者)传给了观察者。
测试一下:
public class ObserverTest { public static void main(String[] args) { WeatherData weatherData = new WeatherData(); CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(weatherData); weatherData.setMeasurements(12, 24, 1.3F); weatherData.setMeasurements(20, 28, 1F); } }
附另外一个是否适合出行的布告板GoingDisplay:
public class GoingDisplay implements Observer, DisplayElement { private Observable observable; private float temperature; private float humidity; private float pressure; public GoingDisplay(Observable observable) { this.observable = observable; observable.addObserver(this); //添加为观察者 } @Override public void display() { boolean isFitGoing = fitGoing(); System.out.println(isFitGoing ? "适宜出行" : "不适合外出"); } private boolean fitGoing() { if (temperature > 15 && temperature < 30 && humidity >= 20 && pressure == 1) return true; return false; } @Override public void update(Observable o, Object arg) { WeatherData weatherData = (WeatherData) o; this.temperature = weatherData.getTemperature(); this.humidity = weatherData.getHumidity(); this.pressure = weatherData.getPressure(); display(); } }
好了,到这里我们把书上关于观察者模式的内容学完了,关于jdk内置的观察者模式,还是建议看一下源码。好了,书上的例子是一个主题和多个观察者,现在我们自己来写多个主题和一个观察者的例子。比如一个用户订阅了一个科技网站,然后还关注了一个电视剧,如果网站有新的咨询,电视剧有更新都会通知这个用户,这就符合观察者模式了,我们来做一下。不用jdk内置的,自己设计。
Observer接口改一下,以主题作为参数
public interface Observer { void update(Subject subject); }
Subject接口和DisplayElement接口和前面的一样
具体的主题:科技网站Website
/** * 一个网站, 有资讯和博客,如果你订阅了或关注了,当有更新时,你会收到更新 */ public class Website implements Subject { private List<Observer> observerList = new ArrayList<Observer>(); private static boolean changed = false; private String message; private String blog; @Override public boolean addObserver(Observer observer) { return observerList.add(observer); } @Override public boolean removeObserver(Observer observer) { return observerList.remove(observer); } @Override public void notifyObservers() { if (observerList != null && observerList.size() > 0) { for (Observer observer : observerList) { observer.update(this); } } } //借鉴jdk内置观察者模式设置一个标记,一边选择性的通知观察者 public void setChanged() { changed = true; } //内容更新时通知观察者 public void setContent(String message, String blog) { this.message = message; this.blog = blog; if (changed) notifyObservers(); changed = false; } @Override public String toString() { return "您订阅的网站有更新: Website [message=" + message + ", blog=" + blog + "]"; } }
类似Website的电视剧TV:
/** * 电视剧作为一个主题,当有更新时通知用户 */ public class TV implements Subject { private List<Observer> observerList = new ArrayList<Observer>(); private static boolean changed = false; private Date updateDate;//更新日期 private Integer episodeNum;//更新到了哪一集 @Override public boolean addObserver(Observer observer) { return observerList.add(observer); } @Override public boolean removeObserver(Observer observer) { return observerList.remove(observer); } @Override public void notifyObservers() { if (observerList != null && observerList.size() > 0) { //通知所有观察者 for (Observer observer : observerList) { observer.update(this); } } } public void setChanged() { changed = true; } //剧集更新时通知所有观察者 /** * 什么时候更新到了第多少集 * @param updateDate * @param episodeNum */ public void episodeUpdate(Date updateDate, Integer episodeNum) { this.updateDate = updateDate; this.episodeNum = episodeNum; if (changed) notifyObservers(); changed = false; } @Override public String toString() { return "您关注的电视剧更新啦,去看吧-=TV [updateDate=" + new SimpleDateFormat("yyyy-MM-dd").format(updateDate) + ", episodeNum=" + episodeNum + "]"; } }
具体的观察者User:
/** * 具体的观察者日,这个没有让其创建时自动注册为观察者,而是调用方法成为观察者,模拟用户自主订阅或关注 */ public class User implements Observer, Display { private Subject subject; public User(Subject subject) { this.subject = subject; } @Override public void display() { if (subject instanceof Website) { Website w = (Website) subject; System.out.println(w.toString()); } else if (subject instanceof TV) { TV tv = (TV) subject; System.out.println(tv.toString()); } } @Override public void update(Subject subject) { //主题把自身当作参数传过来 this.subject = subject; display(); } //订阅或关注,这样自己就成了观察者 public boolean subscribe() { return subject.addObserver(this); } //取消订阅或关注,这样自己从观察者中删除了 public boolean unSubscribe() { return subject.removeObserver(this); } }
测试一下:
/** * 试一下注释掉的代码 */ public class ObserverTest { public static void main(String[] args) { run1(); } public static void run1() { Website website = new Website(); TV tv = new TV(); User user_website = new User(website); User user_tv = new User(tv); //订阅,使自己成为观察者 user_website.subscribe(); user_tv.subscribe(); // user_website.unSubscribe();//取消订阅 website.setChanged(); website.setContent("new message1", "new blog1"); // tv.setChanged(); tv.episodeUpdate(new Date(), 26); } }
好了,这次讲解就这么多了,大家(当然,包括我)要想掌握观察者模式,还需要多多思考练习。