Head First设计模式(阅读笔记)-02.观察者模式

气象监测应用

建立一个应用,利用WeatherData对象取得气象站的数据,并更新三个布告板:目前状况、气象统计和天气预报


要求

  • WeatherData类具有getter方法获取温度、湿度和气压
  • 获取到新的数据时会调用measurementsChanged方法
  • 当有新数据时三个布告板需要更新
  • 系统需要具有扩展性,比如添加新的布告板
简单实现

未使用设计模式的代码如下,存在许多问题:

public class WeatherData{
	public void measurementsChanged(){
        // 获取最新数据
        float temp = getTemperature();
        float humidity = getHumidity();
        float pressure = getPressure();
        
        // 更新布告板
        currentConditionDisplay.update(temp, humidity, pressure);
        statisticDisplay.update(temp, humidity, pressure);
        forcecastDisplay.update(temp, humidity, pressure);
    }
    // 其他方法
}

  • 针对具体实现编程,意味着在后续增加或删除布告板时需要修改程序
  • 没有封装改变的部分,比如更新布告板的实现代码

观察者模式


从订阅报纸入手

先了解下报纸的订阅/取消订阅过程:

  • 当你向某家报社订阅报纸后,每次它们出版新报纸时就会送一份给你
  • 如果你不想在看报纸就可以取消订阅,报社也就不再送了

在观察者模式中,报纸这样的出版者称为主题,我们这样的订阅者称为观察者:

  • 主题对象管理数据,当主题内的数据改变时就会通知观察者
  • 观察者订阅(注册)主题后,每当主题数据改变时能收到更新
  • 同时每个观察者也可以把自己从该主题的观察者集合中删除

定义观察者模式

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


在这里插入图片描述

观察者模式的优势

让主题和观察者之间松耦合


  • 主题只知道观察者实现了Observer接口,它并不需要知道具体观察者是谁以及做了什么
  • 主题唯一依赖的东西是一个实现Observer接口的对象列表,所以任何时候都可以新增/删除观察者
  • 有新的观察者出现时,主题的代码也无需修改,新的类只需要实现观察者接口并且注册为观察者即可
  • 可以单独复用主题或观察者,因为它们非紧耦合

重新设计应用


实现气象站

// 主题接口
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 humidity, float pressure);
}

// 展示内容接口
public interface DisplayElment{
	public void display();  // 布告板需要显示时就调用该方法
}
在WeatherData中实现主题接口

// 具体的主题类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 registerObserver(Observer o){
        observers.add(o);  // 观察者的注册即加入到观察者列表中即可
    }
    public void removeObserver(Observer o){
        int i = observers.indexOf(o);
        if(i >= 0){
            Observers.remove(i);  // 观察者的删除即从列表中移除即可
        }
    }
    public void notifyObservers(){
    	for(int i = 0; i < observers.size(); i++){
            Observer Observer = (Observer)observers.get(i);
            // 调用列表中每个Observer的update方法
            Observer.update(temperature, humidity, pressure);
        }   
    }   
    
    public void measurementsChanged(){  // 在最初的要求中提到了使用该方法进行更新
        notifyObservers();
    }
    public void setMeasurements(float temperature, float humidity, float pressure){
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }
}
建立布告板

以目前状况布告板为例,其他两个类似


public class CurrentConditionsDisplay implements Observer, DisplayElement{
    private float temperature;
    private float humidity;
    private Subject weatherData;
    
    public CurrentConditionsDisplay(Subject weatherData){
        // 目前状况布告板需要把自己注册给weatherData
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }
    
    public void update(float temperature, float humidity, float pressure){
        // 目前状况布告板只需要temperature和humidity
        this.temperature = temperature;
        this.humidity = humidity;
        display();
    }
    public void display(){
        System.out.println("目前状况: " + temperature + "度 " + humidity);
    }
}
启动

public class WeatherStation{
    public static void main(String[] args){
        WeatherData weatherData = new WeatherData();
        CurrentConditionsDisplay c = new CurrentConditionsDisplay(weatherData);
        weatherData.setMeasurements(10,10,10.3f);
    }
}

Java内置的观察者模式

在上述操作中每次都是主题去送所有数据给注册好的观察者们,但是并不是每个观察者需要所有数据,为什么不能让观察者自己选择取需要的数据呢?Java API中内置的观察者模式就实现了两种方式传递数据


在WeatherData中继承Observable

import java.util.Observable;
public class WeatherData extends Observable{
    private float temperature;
    private float humidity;
    private Subject weatherData;
    public WeatherData(){} // 因为这里实现拉取方式,WeatherData也就不需要记录观察者们
    
    // 使用推方式的时候需要使用setMeasurements和measurementsChanged
    public void measurementsChanged(){
        // 该方法用于标记状态状态已经改变,让notifyObservers方法知道该方法被调用时应该更新观察者
        // 这样就不会出现每次更新一点数据就通知观察者,可以设置每次更新到某个阈值再去调用setChanged方法
        setChanged();  
        notifyObservers();
    }
    
    public void setMeasurements(float temperature, float humidity, float pressure){
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }
    
    // getter用于拉方式的时候使用
    public float getTemperature(){
        return temperature;
    }
    public float getHumidity(){
        return humidity;
    }
    public float getPressure(){
        return pressure;
    }
}
重新建立布告板

import java.util.Observable;
import java.util.Observer;
public class CurrentConditionsDisplay implements Observer, DisplayElement{
    Observable Observable;
    private float temperature;
    private float humidity;
    
    public CurrentConditionsDisplay(Observable observable){
        // 目前状况布告板需要把自己注册给weatherData
        this.observable = observable;
        observable.addObserver(this);
    }
    
    // 主题作为第一个变量,好让观察者知道是哪个主题通知它
    public void update(Observable obs, Object arg){
        if(obs instanceof WeatherData){
            WeatherData weatherData = (WeatherData)obs;
            this.temperature = weatherData.getTemperature();
            this.humidity = weatherData.getHumidity();
            display();
        }
    }
    public void display(){
        System.out.println("目前状况: " + temperature + "度 " + humidity);
    }
}
缺点

java.util.ObservableJDK9之后已被标记为过时类


  • java.util.Observable是一个类并非接口,由于Java的单继承机制,所以限制了它的复用
  • setChanged方法被protected修饰,所以要创建Observable实例并组合到自己对象中就必须继承Observable(不是很明白),违法了多用组合,少用继承的设计原则

参考

Head First 设计模式-观察者模式

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值