观察者模式:定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。

知识点的梳理:

  • 为了交互对象之间的松耦合设计而努力;
  • 主题(也就是可观察者)用一个共同的接口来更新观察者;
  • 观察者和可观察者之间用松耦合方式结合,可观察者不知道观察者的细节,只知道观察者实现了观察者接口;
  • 有多个观察者时,不可以依赖特定的通知次序;
  • 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();

}

//当从气象站得到更新观测值时,我们通知观察者

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实例,并组合到自己的对象中。这违反了第二个设计原则:"多用组合,少用继承";

相关文章:

  • 2021-06-25
  • 2021-12-22
  • 2021-07-09
  • 2021-12-04
猜你喜欢
  • 2022-01-01
  • 2022-12-23
  • 2021-12-24
  • 2021-09-03
  • 2022-12-23
  • 2021-11-29
  • 2021-12-09
相关资源
相似解决方案