观察者模式之气象监测站实例演示(一)

最近在阅读HEAD+FIRST+设计者模式时,认为其中的事例非常有趣,于是希望在看的过程中,跟随书直接将代码演示,增进理解。同时将一些内容放到自己的博客中,方便日后查阅,或者其他小伙伴们看。

观察者模式要点:

1、观察者模式定义了对象之间一对多的关系

2、主题(也就是可观察者)用一个共同的接口来更新观察者

3、观察者和可观察者之间用松耦合方式结合(loosecoupling),可观察者不知道观察者的细节,只知道观察者实现了观察者接口

4、使用此模式时,你可从被观察者推(push)或拉(get)数据(然而,推的方式被认为更“正确”)。

5、有多个观察者时,不可以依赖特定的通知次序。

6、Java有多种观察者模式的实现,包括了通用的Java.util.Observable。

7、要注意Java.util.Observable实现上所带来的一些问题

8、如果有必要的话,可以实现自己的Observable,这并不难,不要害怕

9、Swing大量使用观察者模式,许多GUI框架也是如此。

10、此模式也被应用在许多地方,例如:JavaBeans、RMI。

 

观察者模式实例讲解以一个工作合约开始:

    恭喜贵公司获选为敝公司建立下一代Internet气象观测站!该气象站必须建立在我们专利申请中的WeatherData对象上,由WeatherData对象负责追踪目前的天气状况(温度、湿度、气压)。我们希望贵公司能建立一个应用,有三种布告板,分别显示目前的状况、气相统计以及简单的预报。当WeatherData对象获得最新的测量数据时,是那种布告板必须实时更新。

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

   weather-o-Rama气象站有很好的的商业运营模式:一旦客户上钩,他们使用每个布告板都要付钱。最好的部分就是,为了感谢贵公司建立此系统,我们将以公司的认股权支付你。

需求分析:

如上描述了客户的需求,经过分析:

  WeatherData对象知道如何跟物理气象站联系,以取得更新的数据。weatherData对象取得数据,并更新三个布告板。

WeatherData对象有三个方法,getTemperature()、getHumidity()、getPressure() 方法分别获取这些值,

mentsChanged()方法可以通知外界,数值发生变化了!而我们的代码就要在该方法中被调用

当前已知环境:

   1、WeatherData 类具有getter方法,可以取得测量值:温度、湿度与气压

   2、当新的测量数据备妥时,measurementsChanged()方法就会被调用(我们不在乎此方法是如何被调用的,我们只在乎它被调用了)。

  3、我们需要实现三个使用天气数据的布告板:“目前状况“布告、”气相统计”布告、“天气预报”布告。一旦WeatherData有新的测量,这些布告必须马上更新。

  4、此系统必须可扩展,让其他开发人员建立定制的布告板,用户可以随心所欲地添加或删除任何布告板,目前初始的布告板有三类:“目前状况”布告、“气象监测”布告、“天气预告”布告

先简单以一个不妥的方案来简单示意一下,之后再提供完整的设计

    //不妥方案示例
    public void measurementsChanged(){
        float temp=getTempreture();
        int hum=getHumidity();
        float pressure=getPressure();
        CurrentConditionsDisplay.update(temp,hum,pressure);
        ForeCastConditionDisplay.update(temp,hum,pressure);
        SatisfyConditionDisplay.update(temp,hum,pressure);
    }

如上发现,该设计存在多种问题,代码移植困难,不具备可扩展性。按照“变化的进行封装”、“针对接口编程而不是实现编程”的原则,经过仔细分析,按照基本设计原则进行修改调整,最终完成,发现其符合观察者模式的条件:

1、气象数据对策和布告板具备一对多的关系,并且后者紧密依赖前者

2、一个气象数据对象掌握者某些数据状态的变化情况,而布告板需要这些状态值

3、 布告板可能有多种类型,但是有一些基本的特征,即他们都需要调用数据更新的方法。

4、布告板需要实时地关注该对象

(如上理解只是个人理解,描述不恰当的地方还请指正)

鉴于各种原因,先贴上代码

package com.example.zhouxueli.myapplication.ObserverDesign;

public interface Subject {
/**
* 观察者模式中的主题,
* 观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,
* 它的所有依赖者都会受到通知并自动更新,而此处的subject就是其中的一,
* 此处的主题接口,对象使用此接口注册为观察者,或者把自己从观察者中删除
*
* 一个具体主题总是实现主题接口,除了注册和撤销方法之外,具体主题还实现了
* notifyObservers(_)方法,此方法用于在状态改变是更新所有当前观察者
*
* 具体主题也可能有设置和获取状态的方法(稍后会进一步讨论)
* */
    //某对象(观察者)可以通过该方法,将自己注册为该主题的观察者
    void registerObserver(Observer observer);
    //某观察者可以通过该方法,将自己从该主题的观察者中解除
    void removeObserver(Observer observer);
    //该方法用于主题通知其观察者们某方法发生了变化
    void notifyObservers();
}
package com.example.zhouxueli.myapplication.ObserverDesign;

public interface Observer {
    /**
    * 观察者模式中的观察者
    * 每个主题可以有许多观察者
    * 所有潜在观察者必须实现观察者接口,这个接口只有update()一个方法,当主题状态改变时它被调用
    * 具体的观察者可以是实现此接口的任意类。观察者必须注册具体主题,以便接收更新*/
    /**
     * 想要将自己作为Subject的观察者,则必须实现该接口,
     * 因为subject 只能通过该方法通知观察者某数据发生变化*/
    void update(float temp,int humidity,float pressure);
}

 

package com.example.zhouxueli.myapplication.ObserverDesign;

public interface DisplayElement {
    /**该方法为无关紧要的方法,用于更形象地辅助描述观察者模式,
     * 该方法和观察者模式无任何关系,只是让读者更生动深入现场
     * 以更接近实战开发
    */
    void show();
}
package com.example.zhouxueli.myapplication.ObserverDesign;

import java.util.ArrayList;

public class WeatherDataSubject implements Subject {
    /**
     * 气象监测对象  该类演示了观察者模式中的主题角色
     * 因为该类持有气温、气压等状态值的控制权(可以获取改状态值,提供给其他公告板)
     * 当气温、气压发生变化时,该气象监测对象会通知其他用于显示气温、气压内容的对象该值发生
     * 了变化,各个展示系统或者其他应用到该数据的对象据此自行进行相应处理,比如展示新的
     * 气温、气压值
     * 该*/

    //加上一个ArrayList 来记录观察者,此ArrayList是在构造器中建立的
    private ArrayList observers;
    //如下表示三个可变的状态值,该值会动态变化,subject在如下三个值
    //发生变化时会告知其观察者们
    private float tempreture;
    private int humidity;
    private float pressure;

    public WeatherDataSubject(){
        observers = new ArrayList();
    }
    /**
    * subject 可用此方法 将某个对象作为自己的观察者
     * @param Observer 一个实现了观察者接口的具体观察者对象
     *
     * */
    @Override
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }
    /**
     * subject 可将某个观察者从自己的观察者对象列表中移除
     * @param Observer 一个实现了观察者接口的具体观察者对象
     *
     * */
    @Override
    public void removeObserver(Observer observer) {
        int i = observers.indexOf(observer);
        if (i >= 0){
            observers.remove(i);
        }
        observers.remove(observer);
    }
    /**
     * 主题subject通过该方法通知其所有观察者某数据发生变化了
     * 我们把状态高速每一个观察者,因为观察者都实现了update()
     * 所以我们知道如何通知它们。
     * */
    @Override
    public void notifyObservers() {
        for (int i = 0; i < observers.size();i++){
            Observer observer = (Observer) observers.get(i);
            observer.update(tempreture, humidity, pressure);
        }
    }
    /**当气象站检测设备检测到变化时,会通过该方法通知我们气象站
     * */
    public void measurementsChanged(){
        /**当该值发生变化时,主题subject可以在此通知观察者们,*/
        notifyObservers();
    }
    //我们现在只能通过该方法,模拟从检测装置中读取到实际的气象数据,
    //设置值也就相当于气温等监测装置发现气温发生了变化,同时会告知我们
    //(事实上,真实的检测装置也就是这么工作的)
    public void setMeasurements(float tempreture,int humidity,float pressure){
        this.tempreture = tempreture;
        this.humidity = humidity;
        this.pressure = pressure;
        //气温等发生了变化,通过如下方法告知主题,主题发现气温变化了,
        //通知所有的观察者们,
        //你可能会问,为什么该装置不直接跨过主题告诉观察者们,
        //因为该对象就是气象监测装置监测到数据时发送给我们的
        measurementsChanged();
    }

}
//公告板,观察者模式中的观察者之一,即可能还有n多个类似的公告板
    //该公告板一方面要作为天气数据主题的观察者,同时为了展示出来,
    //需要有方法进行展示出来,告知人类。因为可能有n多个公告板,每个
    //公告板可能略有差异,但是展示这个方法是必须有的,所以我们将展示
    //的公用方法抽取出来,作为接口,即,是公告必须具备的功能就是展示

    private float tempreture;
    private int humidity;
    private float pressure;
    private Subject weatherData;
    /**构造器需要weatherData对象(也就是主题)作为注册之用*/
    public  CurrentConditionsDisplay(Subject weatherData){
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }
    //只是把最近的温度展示出来
    @Override
    public void show() {
        System.out.println("Current conditions: "+tempreture+",F degrees and"+
        humidity+"% humidity");
    }

    @Override
    public void update(float temp, int humidity, float pressure) {
        //我们把温度和湿度保存起来,然后调用展示数据的方法
        this.tempreture = temp;
        this.humidity = humidity;
        this.pressure = pressure;
        show();
    }

为了看出效果,模拟有三块公告板,只有show()方法不一样

package com.example.zhouxueli.myapplication.ObserverDesign;

public class ForeCastConditionDisplay implements Observer,DisplayElement{
    //公告板,观察者模式中的观察者之一,即可能还有n多个类似的公告板
    //该公告板一方面要作为天气数据主题的观察者,同时为了展示出来,
    //需要有方法进行展示出来,告知人类。因为可能有n多个公告板,每个
    //公告板可能略有差异,但是展示这个方法是必须有的,所以我们将展示
    //的公用方法抽取出来,作为接口,即,是公告必须具备的功能就是展示

    private float tempreture;
    private int humidity;
    private float pressure;
    private Subject weatherData;
    /**构造器需要weatherData对象(也就是主题)作为注册之用*/
    public ForeCastConditionDisplay(Subject weatherData){
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }
    //只是把最近的气压展示出来,这是和另一个公告板不一样的地方
    @Override
    public void show() {
        System.out.println("Current conditions: "+tempreture+",P pressure and"+
                pressure+"% pressure");
    }

    @Override
    public void update(float temp, int humidity, float pressure) {
        //我们把温度和湿度保存起来,然后调用展示数据的方法
        this.tempreture = temp;
        this.humidity = humidity;
        this.pressure = pressure;
        show();
    }
}
package com.example.zhouxueli.myapplication.ObserverDesign;

public class SatisfyConditionDisplay implements Observer,DisplayElement {
    //公告板,观察者模式中的观察者之一,即可能还有n多个类似的公告板
    //该公告板一方面要作为天气数据主题的观察者,同时为了展示出来,
    //需要有方法进行展示出来,告知人类。因为可能有n多个公告板,每个
    //公告板可能略有差异,但是展示这个方法是必须有的,所以我们将展示
    //的公用方法抽取出来,作为接口,即,是公告必须具备的功能就是展示

    private float tempreture;
    private int humidity;
    private float pressure;
    private Subject weatherData;
    /**构造器需要weatherData对象(也就是主题)作为注册之用*/
    public  SatisfyConditionDisplay(Subject weatherData){
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }
    //只是把最近的温度展示出来
    @Override
    public void show() {
        if (75 == pressure){
            System.out.print("now ,it is the satisyfyPressure");
        }else{
            System.out.print("please adjust the pressure to satisfy pressure");
        }
    }

    @Override
    public void update(float temp, int humidity, float pressure) {
        //我们把温度和湿度保存起来,然后调用展示数据的方法
        this.tempreture = temp;
        this.humidity = humidity;
        this.pressure = pressure;
        show();
    }
}

创建将他们一起连接起来的测试类气象站类

package com.example.zhouxueli.myapplication.ObserverDesign;

public class WeatherStation {
    public static void main(String[] args){
        //气象站需要首先建立一个WeaterDataSubject对象
        WeatherDataSubject weatherDataSubject=new WeatherDataSubject();
        //建立三个布告板,将weatherDataSubject对象传给它
        CurrentConditionsDisplay currentConditionsDisplay=new CurrentConditionsDisplay(weatherDataSubject);
        ForeCastConditionDisplay foreCastConditionDisplay =new ForeCastConditionDisplay(weatherDataSubject);
        SatisfyConditionDisplay satisfyConditionDisplay=new SatisfyConditionDisplay(weatherDataSubject);
       //如下为模拟几项气象监测值,每次发生变化,会通知给气象监测对象
        weatherDataSubject.setMeasurements(80,65,30.4f);
        weatherDataSubject.setMeasurements(82,70,29.3f);
        weatherDataSubject.setMeasurements(78,90,28.2f);
    }
}

如上,完全根据我们的理解,和对Java基本代码的使用进行了创建,在jdk内部,其实有内置的处理该问题的方案,即Observalbe 和 Observer ,后面我将具体代码贴上。

注:

      1、收到评论,说本部分代码拷贝测试时发现问题,经本人自测,发现该部分代码直接粘贴时,可能因为引用包错误的问题,导致编译出错。原因是,如上部分是我们自行推导的解决方案,其中的Observer   Subject等接口,在当前jdk中都有实现。所以在使用该接口,需要导入包时,特别注意,要保证导入这几个接口的相应包是我们自定义的包,而非java.util.包 中的Subject 或者Observer。  

       2、但下部分,即引入jdk自身为了解决该类问题的解决方案。以此处开始区分,前后使用的接口来源注意区分。

二、jdk中的观察者模式体现:

1、Observable 是一个类,而不是一个接口,这点是与上面subject不同的。Observable类追踪所有的观察者,并通知他们。

     Observer 接口其实就是上面的observer。

2、那如何把对象变成观察者。。。。

   如同以前一样,实现观察者接口(Java.util.Observer),然后调用任何Observer对象的addObserver()方法。不想当观察者时,调用deleteObserver()方法就可以了。

3、可观察者如何送出通知。。。

     首先,你需要利用扩展Java.util.Observerable 接口 产生可观察者类,然后,需要两个步骤:

       步骤一:先调用setChanged()方法,标记状态已经改变的事实。

       步骤二:然后调用两种notifyObservers()方法中的一个:

                 notifyObservers() 或 notifyObservers(Object  arg)

    4、观察者如何接收通知。。。

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

       update( Observers  o ,Object  arg);  //当通知时,此版本可以传送任何的数据对象给每一个观察者

  如果你想推送数据给观察者,你可把数据当做数据对象传送给notifyObservers(Object  arg) 方法。否则,观察者就必须从可观察者中拉数据。

  5、实例演示下:

其他方法基本不变,只是将原来的主题和观察者 两个类换一下,测试方法,更改下类名即可。

package com.example.zhouxueli.myapplication.ObserverDesign;

import java.util.Observable;

public class WeatherData extends Observable {

    private float tempreture;
    private int humidity;
    private float pressure;
    //我们不再需要追踪观察者了,也不需要管理注册与删除(让超类代劳即可)
    //所以我们把注册、添加、通知的相关代码删除
    public WeatherData(){ }

    public void measurementsChanged(){
        //setChanged()方法可以让你在更新观察者时,有更多的弹性,你可以更适当地通知
        //观察者。比如,我们在这个例子中,可以考虑增加温度变化一度时才发出通知,否则
        //不通知的逻辑,避免温度变化梯度太小,导致通知过于频繁,通信繁忙的问题
        setChanged();

        //此处没有使用携带数据包的方法,说明我们采用拉的方法,而不是推的方法
        //也就是说,后面的getter()方法将更有意义(相对于推送,用户不会调用getter方法来说
        notifyObservers();
    }
    public void setMeasurements(float tempreture,int humidity,float pressure){
        this.tempreture = tempreture;
        this.humidity = humidity;
        this.pressure = pressure;
        //当检测数据发生变化时,会告诉可观察者对象
        measurementsChanged();
    }

    public float getTempreture() {
        return tempreture;
    }

    public int getHumidity() {
        return humidity;
    }

    public float getPressure() {
        return pressure;
    }
}
package com.example.zhouxueli.myapplication.ObserverDesign;

import java.util.Observable;
import java.util.Observer;

public class Current2ConditionDisplay implements Observer,DisplayElement {

    private Observable observable;
    private float tempreture;
    private int humidity;
    private float pressure;

    @Override
    public void update(Observable o, Object arg) {
        if (arg instanceof WeatherData){    //首先判断类型是否正确,然后采取拉的方式,获取值
            WeatherData weatherData = (WeatherData) arg;
            this.tempreture =((WeatherData) arg).getTempreture();
            this.humidity = ((WeatherData) arg).getHumidity();
            this.pressure = ((WeatherData) arg).getPressure();
            show(); //最后调用展示的方法,该方法只为辅助,让大家更容易接受和理解
        }
    }
    @Override
    public void show() {
        System.out.println("now, the tempreture:"+
                tempreture+",the humidity: "+
                humidity+",the pressure:"+pressure);
    }
}

 

你可能感兴趣的:(java,设计模式)