在经过上次的鸭子游戏中,你使用策略模式成功的解决了各种问题,并且代码的也具有弹性,你的老板给你升职,并且不久后,你接到了一张合约。
这是来自Weather-O-Rama气象站的一份合约,上面说道:
希望贵公司能为他们建立气象观测站,该气象站必须建立在我们壮丽申请的WeatherData
对象上(这个对象是他们之前就有的),由WeatherData
对象负责追踪目前的天气状况(温度、湿度、气压)。我们希望贵公司能建立一个应用,有三种布告板,分别显示状况、气象统计及简单的预报,当WeatherObject
对象获得最新的测量数据是,三种布告板必须实时更新。
而且这是一个可以扩展的气象站,Weather-O-Rama气象站希望公布一组API,好让其他开发人员可以写出自己的气象布告板,并插入此应用中。我们希望贵公司能提供这样的AP。
总的任务需求是建立一个应用,让我们利用WeatherData
对象取得数据,并更新三个布告板目前是:状况、气象统计、天气预报。
隔天早上我们收到了Weather-O-Rama气象站发过来的WeatherData源文件,看了一下代码,一切很直接:
类图画的不是很好,可以讲究点看吧。一旦气象测量更新,此就会调用measurementsChanged
,而你的代码就写在这个方法里面。
我们的工作是实现measurementsChanged方法,好让它更新目前状况,气象天气、天气预报的显示布告板。
Weather-O-Rama气象站的需求说明的并不是很清楚,我们必须搞懂该做些什么。那么,我们目前知道什么呢?
如果有值更新,那么measurementsChanged
方法就会调用
public void measurementsChanged(){
double temperature = getTemperature();
double humidityl = getHumidityl();
double pressure = getPressure();
//调用三个布告板上的更新方法
currentConditionsDisplay.updata(temperature,humidityl,pressure);
statisticDisplay.updata(temperature,humidityl,pressure);
forecastDisplay.updata(temperature,humidityl,pressure);
}
可是这样的代码,真的好吗?在方法中调用三个对象的更新方法,我们这是面对实现编程,这样的代码会导致以后在增加或删除布告板时必须修改程序,仔细观察代码,我们发现这些更新的方法和参数至少是不变的,那么我们是否可以把这些封装起来?
这里的观察者模式就好像生活中的报纸和杂志,来看看它们有什么关系吧。
报社+读者=观察者模式
如果你真的了解了上面的报社和读者的关系,那么你就一定会理解观察者模式,它们的区别只是名称不太一样:报社在观察者模式里面称为主题(Subject),读者在观察者模式中成为观察者(Observer)
当使用观察者模式后,就像报社和读者的关系一样,读者可以随时订阅或取消,报社只有有新报就会发给订阅报纸的读者。
白箭头虚线— 实现接口
黑箭头实线— 有一个
当两个对象之间的松耦合,它们依然可以交互,但是不太清楚彼此的细节。
观察者模式提供了一种对象设计,让主题和观察者之间松耦合
为什么这么说?
首先你要知道观察者模式分两个角色:主题和观察者,上面已经说明了这2个角色的关系。
在主题(Subject)中,主题只知道观察者实现了某个接口(上面图中的Observer接口),主题不需要知道观察者的具体类是谁,做了什么或者任何细节。就好像你在小卖部买东西,老板不需要知道你买这个东西干嘛,他只需要知道你只要付款,才能拿到东西。
在运行时,我们可以动态的更换观察者,而且主题也不会受到影响,而且我们还可以改变主题或观察者其中一方,并不会影响另一方。因为两者都是松耦合的,所有只需要他们直接的接口仍被遵循,我么就可以自由的改变他们。
在气象观测站系统中,WeatherData
类就像是报社,专业的来说WeatherData
类是主题,那么观察者就是布告板了。
那么我们首先创建一个主题接口
//主题
public interface Subject {
//添加观察者
public void registerObserver(ObServer o);
//删除观察者
public void removeObserver(ObServer o);
//当数据更新时,告知观察者
public void notifyObservers();
}
观察者接口
//观察者
public interface ObServer {
public void update(float temp,float hmidity,float pressure);
}
每个布控板的显示方式不一样,不同的布控板显示的数据不一样,所有我们在创建一个显示接口,来让布控板实现。
显示接口
//布控板的显示方式
public interface DisplayElement {
public void display();
}
在这里接口都写完了,该写实现了。
我们把Weather-O-Rama气象站发给我们的WeatherData类实现主题接口。
public class WeatherData implements Subject{
//观察者
private ArrayList observer;
//温度
private float temperature;
//湿度
private float humidityl;
//气压
private float pressure;
public WeatherData() {
this.observer = new ArrayList();
}
public double getTemperature() {
return temperature;
}
public void setTemperature(float temperature) {
this.temperature = temperature;
}
public double getHumidityl() {
return humidityl;
}
public void setHumidityl(float humidityl) {
this.humidityl = humidityl;
}
public double getPressure() {
return pressure;
}
public void setPressure(float pressure) {
this.pressure = pressure;
}
@Override
public void registerObserver(ObServer o) {
observer.add(o);
}
@Override
public void removeObserver(ObServer o) {
int i = observer.indexOf(o);
if(i>=0){
observer.remove(i);
}
}
@Override
public void notifyObservers() {
for (int i = 0; i
这里再写一个主控板的实现,还有两个当做练习:
状况布控板
public class CurrentConditionsDisplay implements ObServer ,DisplayElement{
//观察者
private Subject weatherData;
//温度
private float temperature;
//湿度
private float humidityl;
public CurrentConditionsDisplay(Subject weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
@Override
public void update(float temp, float hmidity, float pressure) {
this.temperature=temp;
this.humidityl=hmidity;
display();
}
@Override
public void display() {
System.out.println("状况布控板:当前温度:"+temperature+"当前湿度"+humidityl);
}
}
看到这里你也许会问为什么要加观察者进来,因为这样更方便我们找到观察者,然后可以进行添加,删除操作。
测试
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData);
weatherData.setMeasurements(12.1f,51.2f,52.3f);
weatherData.removeObserver(currentConditionsDisplay);
weatherData.setMeasurements(12.1f,51.2f,52.3f);
}
输出
状况布控板:当前温度:12.1当前湿度51.2
注意:这里并不会输出2次,因为在更新之后,状况布控板取消了继续监听,所以主题以后更新都不会通知状况布控板了
java其实是内置观察者模式的,但是和我们说观察者模式的有一点小差异。
这里的主题是一个具体的类,而不是一个接口。在Java内置的观察者模式中主题是Observable
类。
继承Observable
类以后,需要2个步骤:
1.先调用setChanged()
标记转态已经改变
2.然后调用notifyObservers()
或notifyObservers(Object arg)
方法中的一个。
有惨的方法
notifyObservers()
表示不传任何数据,让观察者自己拉数据。有参的方法
notifyObservers(Object arg)
表示传递参数过去,让观察者可以自己获取,也可以直接获取。
观察者没有什么太大的变化,也是一个接口,但是和我们自己写的观察者模式中多了一个参数。void update(Observable o, Object arg);
第一个参数是主题本身,好让观察者知道是哪一个主题
第二个参数是观察者传入的数据
WeatherData类
public class WeatherData extends Observable{
//温度
private float temperature;
//湿度
private float humidityl;
//气压
private float pressure;
public double getTemperature() {
return temperature;
}
public void setTemperature(float temperature) {
this.temperature = temperature;
}
public double getHumidityl() {
return humidityl;
}
public void setHumidityl(float humidityl) {
this.humidityl = humidityl;
}
public double getPressure() {
return pressure;
}
public void setPressure(float pressure) {
this.pressure = pressure;
}
//值如果变化就会调用此方法
public void measurementsChanged(){
setChanged();
//在这里演示没有参数的方法,表示观察者要自己拉
notifyObservers();
}
//模拟更新气象站数据
public void setMeasurements(float temp,float hmidity,float pressure){
this.temperature=temp;
this.humidityl=hmidity;
this.pressure=pressure;
measurementsChanged();
}
}
状况布控板
public class CurrentConditionsDisplay implements ObServer ,DisplayElement{
//观察者
private Subject weatherData;
//温度
private float temperature;
//湿度
private float humidityl;
public CurrentConditionsDisplay(Subject weatherData) {
this.weatherData = weatherData;
weatherData.registerObserver(this);
}
@Override
public void update(Observable o, Object arg) {
if(o instacof WeatherData){
WeatherDatas weather=(WeatherDatas)o;
temp=weather.getTemperature();
hmidity=weather.getHmidity();
}
display();
}
@Override
public void display() {
System.out.println("状况布控板:当前温度:"+temperature+"当前湿度"+humidityl);
}
}
整合完毕!
用内置的API,虽然方便,但是没有自己写的灵活,总之2种都会是最好的,看需求用吧