您的位置:首页 > 其它

设计模式--观察者模式(五)

2017-06-09 22:53 169 查看
观察者模式:定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。
知识点的梳理:
为了交互对象之间的松耦合设计而努力;
主题(也就是可观察者)用一个共同的接口来更新观察者;
观察者和可观察者之间用松耦合方式结合,可观察者不知道观察者的细节,只知道观察者实现了观察者接口;
有多个观察者时,不可以依赖特定的通知次序;

Swing大量使用观察者模式,许多GUI框架也是如此;
此模式也被应用到许多地方,如JavaBean,RMI;
  

气象监测应用的概况

此系统中的三个部分是气象站(获取实际气象数据的物理装置),WeatherData对象(追踪自来气象站的数据,并更新布告板)和布告板(显示目前天气状况给用户看);



WeatherData对象知道如何跟物理气象站联系,以取得更新的数据。WeatherData对象会随即更新三个布告板的显示:目前状况(温度,湿度,气压),气象统计和天气预报;
我们需要建立一个应用,利用WeatherData对象取得数据,并更新三个布告板:目前状况,气象统计和天气预报;

我们现在知道什么?
WeatherData类具有getter方法,可以取得三个测量值:温度,湿度与气压:

getTemperature();

getHumidity();
getPressure();

当新的测量数据备妥时,measurementsChanged()方法就会被调用:

measurementsChanged()

需要实现三个使用天气数据的布告板:"目前状况"布告,"气象统计"布告,"天气预报"布告。一旦WeatherData有新的测量,这些布告必须马上更新;
此系统可扩展,让其他开发人员建立定制的布告板,用户可以随心所欲地添加或删除任何布告板。

错误示范
在measurementsChanged()方法中添加代码:

public class WeatherData {

public void measurementsChanged(){
//调用WeatherData的三个getXxx()方法,以取得最近的测量值。这些getXxx()方法已经实现好了
float
temp = getTemperature();
float
humidity = getHumidity();
float
pressure = getPressure();
//更新布告板
currentConditionsDisplay.update(temp,humidity,pressure);
statisticsDisplay.update(temp,humidity,pressure);
forecastDisplay.update(temp,humidity,pressure);
}
//其他WeatherData方法
}

实现有什么不对?



什么是观察者模式

报纸和杂志的订阅是怎么回事?
报社的业务就是出版报纸;
向某家报社订阅报纸,只要他们有新报纸出版,就会给你送来。只要你是他们的订户,你就会一直收到新报纸;
当你不想再看报纸的时候,取消订阅,他们就不会再送新报纸;
只要报社还在运营,就会一直有人(或单位)向他们订阅报纸或取消订阅报纸;

出版者+订阅者=观察者模式

结合报纸的订阅:出版者改称为"主题"(subject),订阅者改称为"观察者"(Observer);



图解观察者模式













定义观察者模式:类图



松耦合的威力
两个对象之间松耦合,它们依然可以交互,但是不太清楚彼此的细节;
对于观察者,主题只知道观察者实现了某个接口(也就是Observer接口),主题不需要知道观察者的具体类是谁,做了些什么或其他任何细节;
任何时候都可以增加新的观察者。因为主题唯一依赖的东西是一个实现Observer接口的对象列表,所以可以随时增加观察者。实际上,在运行的时候,可以用新的观察者取代现有的观察者,主题不会受到任何影响。同理,也可以在任何时候删除这些观察者;

有新类型的观察者出现时,主题的代码不需要修改。
加入我们有个新的具体类需要当观察者,我们不需要为了兼容新类型而修改主题的代码,所有要做的就是在新的类里实现此观察者接口,然后注册为观察者即可;
主题不在乎别的,它只会发送通知给所有实现了观察者接口的对象;

可以独立地复用主题或者观察者。如果我们在其他地方需要使用主题或观察者,可以轻易地复用,因为二者并非紧耦合;
改变主题或者观察者其中一方,并不会影响另一方。因为两者是松耦合的,所以只要他们之间的接口仍被遵守,就可以自由的改变它们;

设计气象站
WeatherData类符合观察者模式中,所说的"一",而"多"正是使用天气观测的各种布告板;

如何将气象观测值放到布告板上呢?
如果将WeatherData对象当作主题,把布告板当作观察者,布告板为了取得信息,就必须先向WeatherData对象注册;
一旦WeatherData知道有某个布告板的存在,就会适时地调用布告板的某个方法来告诉布告板观测值是多少;
每个布告板都有差异,需要一个共同的接口。虽然布告板的类不一样,但它们应该实现相同的jie'k
所以,每个布告板都应该有一个名为update()的方法,以供WeatherData对象调用;而这个方法应该在所有布告板都实现的共同接口里定义;

设计图





实现气象站
先建立一些接口

public interface Subject {

//这两个方法都需要一个观察者作为变量,该观察者是用来注册或被删除的
void registerObserver(Observer
o);
void removeObserver(Observer
o);
//当主题状态改变时,这个方法会被调用,以通知所有的观察者
void notifyObservers();
}

public interface Observer {

/**
*
所有观察者都必须实现此方法,以实现观察者接口。把观测值传入观察者
*
当气象观测值改变时,主题会把这些状态值当作方法的参数,传送给观察者
*/
void update(float
temp,float
humidity,float
pressure);
}

/*

*
该接口只包含一个方法。当布告板需要显示时,调用此方法。

*/

public interface DisplayElement {

public void display();
}

在WeatherData中实现主题接口

public class WeatherData
implements Subject {

//此字段用来记录观察者,在构造函数中建立
private ArrayList
observers;
private float
temperature;
private float
humidity;
private float
pressure;
public WeatherData(){
observers =
new ArrayList();
}
//当从气象站得到更新观测值时,我们通知观察者<
16d15
span style="font-family:'宋体';">
public void measurementsChanged(){
notifyObservers();
}
  

//当注册观察者时,只要把它加到ArrayList的后面即可
@Override
public void registerObserver(Observer
o) {
observers.add(o);
}
  

//当观察者想取消注册,我们把它从ArrayList中删除即可
@Override
public void removeObserver(Observer
o) {
int
i =
observers.indexOf(o);
if(i >= 0 ){
observers.remove(i);
}
}
  

//把状态告诉每一个观察者。因为观察者都实现了update(),所以可以通知它们
@Override
public void notifyObservers() {
for (int
i = 0;
i < observers.size();
i++) {
Observer
observer = (Observer)
observers.get(i);
observer.update(temperature,
humidity,
pressure);
}
}
//利用这个方法来测试布告板
public void setMeasurements(float
temperature,float
humididty,float
pressure){
this.temperature =
temperature;
this.humidity =
humididty;
this.pressure =
pressure;
measurementsChanged();
}
//其他WeatherData方法
}

建立布告板
需要三个布告板:目前状况布告板,统计布告板,预测布告板。先来实现目前状况布告板

  

/**

*
实现了Observer接口,所以可以从WeatherData对象中获得改变

*
实现了DisplayElement接口,因为我们的API规定所有的布告板都必须实现此接口

*/

public class CurrentConditionsDisplay
implements Observer, DisplayElement {

private float
temperature;
private float
humidity;
private Subject
weatherData;
//构造器需要weatherData对象(也就是主题)作为注册使用
public CurrentConditionsDisplay(Subject
weatherData){
this.weatherData =
weatherData;
weatherData.registerObserver(this);
}
//该方法只是显示最近的温度和湿度
@Override
public void display() {
System.out.println("Current conditions:"+temperature+"F
degrees and"+humidity+"% humidity");
}
  

@Override
public void update(float
temp,
float humidity,
float pressure) {
//当update()被调用时,我们把温度和湿度保存起来,然后调用display()
this.temperature =temperature;
this.humidity =
humidity;
display();
}
}

启动气象站
先建立一个测试程序

public class WeatherStation {

public static void main(String[]
args) {
//首先,建立一个WeatherData对象
WeatherData
weatherData =
new WeatherData();
CurrentConditionsDisplay
currentDisplay =
new CurrentConditionsDisplay(weatherData);
weatherData.setMeasurements(80, 65, 30.4f);
weatherData.setMeasurements(82, 70, 29.2f);
weatherData.setMeasurements(78, 90, 29.2f);
}
}

效果:



使用Java内置的观察者模式

java.util包(package)内包含最基本的Observer接口与Observable类,这和Subject接口与Observer接口很相似;
有了Java内置的支持,只需要扩展(继承)Observable,并告诉它何时该通知观察者就可以了;

java.util.Observer和java.util.Observable的类结构



如何把对象变成观察者?
实现观察者接口(java.util.Observer),然后调用任何Observable对象的addObserver()方法。不想再当观察者,调用deleteObserver()方法即可;

观察者如何送出通知?

需要利用扩展java.util.Observable接口产生"可观察者"类,然后,需要两个步骤:
先调用setChanged()方法,标记状态已经改变的事实;

然后调用两种notifyObservers()方法中的一个:
notifyObservers();
notifyObservers(Object arg);//当通知时,此版本可以传送任何的数据对象给每一个观察者

观察者如何接受通知?

同以前一样,观察者实现更新方法,但签名不一样:



推送数据给观察者,可以把数据当作数据对象传送给ntifyObservers(args)方法。否则,观察者就必须从可观察者对象中"拉"(pull)数据。

利用内置的支持重做气象站
首先,把WeatherData改成使用java.util.Observable

package hey.up2;

//导入Java内置观察者package

import java.util.Observable;

import java.util.Observer;

  

//现在继承Observable

public class WeatherData
extends Observable {

private float
temperature;
private float
humidity;
private float
pressure;
public WeatherData(){
//不再需要构造函数来记住观察者们了
}
//当从气象站得到更新观测值时,我们通知观察者
public void measurementsChanged(){
//在调用notifyObservers函数之前,要先调用serChanged()来指示状态已经改变
setChanged();
notifyObservers();
}
//利用这个方法来测试布告板
public void setMeasurements(float
temperature,float
humididty,float
pressure){
this.temperature =
temperature;
this.humidity =
humididty;
this.pressure =
pressure;
measurementsChanged();
}
//观察者需要这些getter方法,取得WeatherData对象的状态
public float getTemperature() {
return
temperature;
}
public float getHumidity() {
return
humidity;
}
public float getPressure() {
return
pressure;
}
}

现在重做CurrentConditionsDisplay

package hey.up2;

import java.util.Observable;

import java.util.Observer;

/**

*
实现java.util.Observer接口

*/

public class CurrentConditionsDisplay
implements Observer, DisplayElement {

Observable
observable;
private float
temperature;
private float
humidity;
//现在构造函数需要一个Observable当参数,并将CurrentCondi-tionsDisplay对象登记成为观察者
public CurrentConditionsDisplay(Observable
observable){
this.observable =
observable;
observable.addObserver(this);
}
  
//改变update方法,增加Observable和数据对象作为参数
@Override
public void update(Observable
obs, Object
arg) {
//此方法先确定可观察者属于WeatherData类型,然后利用getter方法获取温度和湿度测试值,最后调用display()
if(obs
instanceof WeatherData){
WeatherData
weatherData = (WeatherData)obs;
this.temperature =
weatherData.getTemperature();
this.humidity =
weatherData.getHumidity();
display();
}
}
//该方法只是显示最近的温度和湿度
@Override
public void display() {
System.out.println("Current conditions:"+temperature+"F
degrees and"+humidity+"% humidity");
}
}

继续使用WeatherStation进行测试,效果如下:其实是一样的!



java.util.Observable的缺点
它一个类,而非接口;
Observable的setChanged()方法的访问修饰符为protected。除非我们继承自Observable,否则无法创建Observable实例,并组合到自己的对象中。这违反了第二个设计原则:"多用组合,少用继承";
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  设计模式