上次学习了策略模式,这次来学习观察者模式。这次先把书上的例子学习一下,然后再自己写一个例子,看是否能做到举一反三(或者说触类旁通),不过要想真正的掌握还要多多思考和练习。
学习书上的例子
现在我们有一个任务,需要根据天气状况来发布不同的布告,开始有3个布告板:当前状况,气象统计,天气预报。像这样的:
现在有一个天气情况的类WeatherData,可以设置和获取温度temperature,湿度humidity和气压pressure数据。要在天气变化时通知布告板,布告板更新以显示不同的值。开始可能想要这样做
我们以后需要添加新的布告板或者删除布告板,这就要求系统具有弹性,如果是按照上图这样做,每添加或删除布告板都需要修改WeatherData类。好吧,既然这样,我想我们需要来使用观察者模式了。
认识观察者模式
加入用户订阅了报纸,报社只要有新的报纸就会给用户送过去,当用户不想看报纸了,就取消订阅,报社就不会送新的报纸过去了。在这里就是一个观察者模式,不过需要改一个名字:报社称为“主题”,用户称为“观察者”。只要主题有更新就会通知观察者,观察者然后更新自身状态。想想天气的例子是不是很类似,所以我们把观察者模式运用在天气的例子中,于是在我们的天气的例子中,天气状况就是主题,布告板就是观察者,只要天气状况发生改变就会通知布告板,然后布告板收到通知并改变自身状态。
定义观察者模式
在真实世界中,你通常会看到观察者模式被定义成:定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
观察者模式类图
通常观察者模式被设计成包含Subject和Observer接口。看一下类图
软件设计原则
观察者模式通过实现接口的方式实现针对接口编程(这也是一种设计原则,上次说过,这里的接口不一定是interface关键字关键字修饰的接口)建立更有弹性的系统,使对象间的互相依赖降到了最低,这就是所说的松耦合。所以这里的设计原则是:为了对象之间的松耦合而努力。
使用观察者模式来写天气的例子
观察者接口Observer:
/**
* 观察者中只有一个update方法
*/
public interface Observer {
void update(float temp, float humidity, float pressure);
}
这里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);
}
}
好了,这次讲解就这么多了,大家(当然,包括我)要想掌握观察者模式,还需要多多思考练习。