入门设计模式之观察者模式

入门设计模式之观察者模式

在经过上次的鸭子游戏中,你使用策略模式成功的解决了各种问题,并且代码的也具有弹性,你的老板给你升职,并且不久后,你接到了一张合约。

任务需求

这是来自Weather-O-Rama气象站的一份合约,上面说道:

希望贵公司能为他们建立气象观测站,该气象站必须建立在我们壮丽申请的WeatherData对象上(这个对象是他们之前就有的),由WeatherData对象负责追踪目前的天气状况(温度、湿度、气压)。我们希望贵公司能建立一个应用,有三种布告板,分别显示状况、气象统计及简单的预报,当WeatherObject对象获得最新的测量数据是,三种布告板必须实时更新。

而且这是一个可以扩展的气象站,Weather-O-Rama气象站希望公布一组API,好让其他开发人员可以写出自己的气象布告板,并插入此应用中。我们希望贵公司能提供这样的AP。

拆分任务需求

总的任务需求是建立一个应用,让我们利用WeatherData对象取得数据,并更新三个布告板目前是:状况、气象统计、天气预报。

WeatherData类

隔天早上我们收到了Weather-O-Rama气象站发过来的WeatherData源文件,看了一下代码,一切很直接:

类图画的不是很好,可以讲究点看吧。一旦气象测量更新,此就会调用measurementsChanged,而你的代码就写在这个方法里面。

我们的工作是实现measurementsChanged方法,好让它更新目前状况,气象天气、天气预报的显示布告板。

目前我们知道什么?

Weather-O-Rama气象站的需求说明的并不是很清楚,我们必须搞懂该做些什么。那么,我们目前知道什么呢?

  • WeatherData类具有getter方法,可以取得三个测量值:温度、湿度与气压。(类图中没有画出来)
  • 当新的测量数据备妥时,measurementsChanged方法就会被调用(我们不在乎方法是如何被调用的,我们之在乎它被调用了)
  • 我们需要实现三个使用天气数据的布告板:“目前状况”布告、”气象统计”布告、“天气预报”布告。一旦WeatherData有新的测量,这些布告必须马上更新。
  • 此系统必须可扩展,让其他开发人员建立定制布告板,用户可以随性所欲地添加或删除任何布告板。目前初始的布告板有三类:“目前状况”布告、“气象统计”布告和“天气预报”布告。

第一次尝试

如果有值更新,那么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);
}

可是这样的代码,真的好吗?在方法中调用三个对象的更新方法,我们这是面对实现编程,这样的代码会导致以后在增加或删除布告板时必须修改程序,仔细观察代码,我们发现这些更新的方法和参数至少是不变的,那么我们是否可以把这些封装起来?

引入观察者模式

这里的观察者模式就好像生活中的报纸和杂志,来看看它们有什么关系吧。

  1. 报纸的业务就是出报
  2. 向某家社保订阅报纸,只要他们有新报纸出版,就会给你送来,只要你是他们的订户,你就会一直收到新报纸。
  3. 只要报社还在运营,就会一直有人向他们订阅报纸或取消报纸

报社+读者=观察者模式

如果你真的了解了上面的报社和读者的关系,那么你就一定会理解观察者模式,它们的区别只是名称不太一样:报社在观察者模式里面称为主题(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 <observer.size() ; i++) {
            ObServer obServer= (ObServer) observer.get(i);
            obServer.update(temperature,humidityl,pressure);
        }
    }

    //值如果变化就会调用此方法
    public  void measurementsChanged(){
        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(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其实是内置观察者模式的,但是和我们说观察者模式的有一点小差异。

主题类(Observable)

这里的主题是一个具体的类,而不是一个接口。在Java内置的观察者模式中主题是Observable类。

继承Observable类以后,需要2个步骤:

1.先调用setChanged()标记转态已经改变

2.然后调用notifyObservers()notifyObservers(Object arg)方法中的一个。

有惨的方法notifyObservers()表示不传任何数据,让观察者自己拉数据。

有参的方法notifyObservers(Object arg)表示传递参数过去,让观察者可以自己获取,也可以直接获取。

观察者类(Observer)

观察者没有什么太大的变化,也是一个接口,但是和我们自己写的观察者模式中多了一个参数。void update(Observable o, Object arg);

第一个参数是主题本身,好让观察者知道是哪一个主题

第二个参数是观察者传入的数据

使用内置的API重勾气象站

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种都会是最好的,看需求用吧

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值