合工大很牛很牛牛

  BlogJava :: 首页 :: 联系 :: 聚合  :: 管理
  14 Posts :: 1 Stories :: 37 Comments :: 0 Trackbacks
 

下面做一个天气预报系统,涉及到的参数一共有三种:temperature, humidity, pressure。外围接受显示装置也分为三种:current condition display会列出当前的气温,湿度和气压;statistics display会列出当前的温度最高,最低和平均值;forecast display列出将来预测的天气状况。现在我们设想用一个WeatherDataObject这个对象从气象台实时接受数据,再把更新的数据发送到外围接受显示装置上去。如下:

我们先看右边的三个显示设备,我们把它跟前一章的Duck做比较:Duck是一个总称,被做成抽象类,下面有许多各种各样的duck,被做成该抽象类的子类,Duck的各种会变化的功能比如quackableflyable被分别做成接口,每个接口下面有各种具体的功能实现它。同理,这个天气情况显示设备也好比是一个总称,下面有三种显示设备,而它的可变化的功能是display,因为未来可能再做第四种设备是用声音报警的。。。

因为是实时接受和显示数据,所以所有的设备必然包含update()功能,部分设备有display功能。UML如下:

public interface Observer
{

    public void update(float temperature, float humidity, float pressure);
}

public interface Display
{

    public void display();
}

public class CurrentCondition implements Observer, Display
{

    private float temperature;
    private float humidity;

    public void display()
    {
        System.out.println("Current conditions: " + temperature + "F degrees and humidity " + humidity);
    }

    public void update(float temperature, float humidity, float pressure)
    {
        this.temperature = temperature;
        this.humidity = humidity;
        display();
    }
}

public class Statistics implements Display, Observer
{

    private static float maxT = 0;//历史最高气温
    private static float minT = 100;//
历史最低气温
    private static float sumT = 0;//
气温总和
    private static int num = 0;//
测量次数
    public void display()
    {
        System.out.println("Max Temperature:"+maxT);
        System.out.println("Min Temperature:"+minT);
        System.out.println("Average Temperature:"+sumT/num);
    }

    public void update(float temperature, float humidity, float pressure)
    {
        if (temperature < minT)
        {
            minT = temperature;
        }
        if (temperature > maxT)
        {
            maxT = temperature;
        }
        sumT += temperature;
        num++;
        display();
    }
}

public class Forecast implements Display, Observer
{

   private static float currentPressure = 29;//设定一个当前气压的默认值,为简化操作
    private static  float lastPressure = 29;//
设定一个先前气压的默认值

    public void display()
    {
        System.out.println("Forecast:");
        if (currentPressure > lastPressure) //
气压升高,天气转暖;气压降低,天气转凉
        {
            System.out.println("will be warmmer.");
        }
        else if (currentPressure == lastPressure)
        {
            System.out.println("will be the same");
        }
        else
        {
            System.out.println("will be cooler");
        }
    }

    public void update(float temperature, float humidity, float pressure)
    {
        lastPressure = currentPressure;
        currentPressure = pressure;
        display();
    }
}

 

现在着重考虑WeatherData 这个对象怎么写,该对象首先要能够从气象站收取气温,湿度,气压的信息(通过气象台使用setMeasurements方法来设定WeatherData中的各属性的值),并向终端显示设备更新气象信息(使用upadeDisplay方法),所以有:

public class WeatherData
{
    private float temperature;
    private float humidity;
    private float pressure;

    public void updateDisplay()
    {
        CurrentCondition currentCondition=new CurrentCondition();
        Statistics statistics=new Statistics();
        Forecast forecast=new Forecast();
       
currentCondition.update(temperature, humidity, pressure); //这部分是可变动的,可是并没有被分离出去
        statistics.update(temperature, humidity, pressure);
        forecast.update(temperature, humidity, pressure);

    }

    public void setMeasurements(float termperature, float humidity, float pressure)
    {
        this.temperature=termperature;
        this.humidity=humidity;
        this.pressure=pressure;
        updateDisplay();
    }
}

 

最后我们用个主函数来调试一下:

public class Main
{

    public static void main(String[] args)
    {
        WeatherData wd=new WeatherData();
        wd.setMeasurements(20, 10, 15);
        wd.setMeasurements(21, 11, 16);
    }
}

输出:

Current conditions: 20.0F degrees and humidity 10.0
Max Temperature:20.0
Min Temperature:20.0
Average Temperature:20.0
Forecast:
will be cooler
Current conditions: 21.0F degrees and humidity 11.0
Max Temperature:21.0
Min Temperature:20.0
Average Temperature:20.5
Forecast:
will be warmmer.

可以看出,一切运行正常,是不是这样,该程序就搞定了呢?当然不是!当我们不想让三个终端中的某一个接受天气信息时,或者我们要增加一个新的终端来显示其他的一些天气信息时,我们势必要改写WeatherDate中的updateDisplay方法(上面源代码中的红字部分)。我们并没有把WeatherDate中的不变化的setMeasurements方法和变化的updateDisplay方法分离开,也就是说没有隔离出变化的部分,所以这个程序有待改进。

可不可以把updateDisplay写成接口的形式分离出去呢?Good Idea! 不过updateDisplay之所以产生变化,不是像Duckflyable那样是功能性的变化,而是updateDisplay方法体本身中的执行过程需要增减。所以即使把updateDisplay作为接口分离出去,这个接口也要不断的改变,多以我们对这类增减某一类似问题所产生的变化引入observer pattern

所谓的observer pattern即是实现观察者和观察对象之间关系的一种模式。该模式中,观察对象可以有一个,也可以有多个。如果观察者要实时接受观察对象的信息,就必须通过该观察对象的registerObserver方法注册成为该观察对象的观察者群。也可以通过removeObserver方法注销并离开该观察对象的观察者群。观察对象通过notifyObserver方法将实时信息通知给它所有的已注册的观察者。

observer pattern的本质实质是1对多的关系:每一个观察对象可以对应着许多观察者,而每一个观察者又可以观察许多观察对象

 

把这种思路放到目前我们需要解决的气象站的例子上去:

 

较前面的实现方式,主要变化的代码如下:

public interface Subject
{
     //
之所以要写一个接口而不直接使用WeatherData类,是因为该接口下面可能不止WeatherData这一个实现对象
    public void registerObserver(
Observer o);

    public void removeObserver(Observer o);

    public void notifyObservers();
}

 

import java.util.ArrayList;

public class WeatherData implements Subject
{

    private float temperature;
    private float humidity;
    private float pressure;
    private ArrayList<Observer> observers = new ArrayList<Observer>();

    public WeatherData()
    {
    }

    public void setMeasurement(float temperature, float humdity, float pressure)
    {   //
气象站使用该方法设置新的气象数值,并通知所有已注册观察者
        this.temperature = temperature;
        this.humidity = humdity;
        this.pressure = pressure;
        notifyObservers();
    }

    public void registerObserver(Observer o) //注册观察者
    {
        observers.add(o);
    }

    public void removeObserver(Observer o) //注销观察者
    {
        if (observers.indexOf(o) >= 0)
        {
            observers.remove(o);
        }
    }

    public void notifyObservers() //通知所有的已注册的观察者新信息
    {
        for (Observer o : observers)
        {
            o.update(temperature, humidity, pressure);
        }
    }
}

public class Main
{
    public static void main(String[] args)
    {
        WeatherData wd=new WeatherData();
        CurrentCondition cc=new CurrentCondition();
        wd.registerObserver(cc); //
注册CurrentCondition显示方式到WeatherData的观察者群中去
        Statistics st=new Statistics();
        wd.registerObserver(st);   //
同理,注册Statistics的显示方式
        wd.setMeasurement(20, 10, 15);
        wd.setMeasurement(21, 11, 16);       
        System.out.println("===================
我是分割线=================");
        wd.removeObserver(st); //
注销Statistics的显示方式
        wd.setMeasurement(19, 9, 14);
    }
}

 

运行结果如下;

Current conditions: 20.0F degrees and humidity 10.0
Max Temperature:20.0
Min Temperature:20.0
Average Temperature:20.0
Current conditions: 21.0F degrees and humidity 11.0
Max Temperature:21.0
Min Temperature:20.0
Average Temperature:20.5
===================
我是分割线=================
Current conditions: 19.0F degrees and humidity 9.0

 

由此,我们可以看到,我们能够在Main方法中动态的注册和注销某被观察对象的已注册的观察者,达到了我们预期的目的。

现在我们尝试新增一个显示方式HeatIndexheat index是热指数,它的计算方法为:heatIndex=16.923+1.85212*temperature+5.37941*humidity

很简单,我们只要写一个新的类,实现ObserverDisplayElement接口就可以了,在主程序Main中,我们再把这个显示方式注册到WeatherData的观察者中去。

public class HeatIndex implements Observer, Display
{

    private float temperature;
    private float humidity;

    public HeatIndex()
    {
    }

    public void display()
    {
        float heatIndex = (float) (16.923 + 1.85212 * temperature + 5.37941 * humidity);
        System.out.println("Heat Index is " + heatIndex);
    }

    public void update(float temperature, float humidity, float pressure)
    {
        this.humidity = humidity;
        this.temperature = temperature;
        display();
    }
}

public class Main
{

    public static void main(String[] args)
    {
        WeatherData wd = new WeatherData();
        CurrentCondition cc = new CurrentCondition();
        wd.registerObserver(cc);
        Statistics st = new Statistics();
        wd.registerObserver(st);
        wd.setMeasurement(20, 10, 15);
        wd.setMeasurement(21, 11, 16);
        System.out.println("===================
我是分割线=================");
        wd.removeObserver(st);
    
   HeatIndex hi = new HeatIndex();
        wd.registerObserver(hi);  
    
        wd.setMeasurement(19, 9, 14);
    }
}

运行结果如下:

Current conditions: 20.0F degrees and humidity 10.0
Max Temperature:20.0
Min Temperature:20.0
Average Temperature:20.0
Current conditions: 21.0F degrees and humidity 11.0
Max Temperature:21.0
Min Temperature:20.0
Average Temperature:20.5
===================
我是分割线=================
Current conditions: 19.0F degrees and humidity 9.0
Heat Index is 100.52797

可见,我们很轻松的就添加了一个显示方式,并动态的注册到相应的观察者中,而且最重要的是:我们没有对源代码做任何更改。

 

posted on 2008-07-18 00:54 化的了 阅读(1624) 评论(2)  编辑  收藏 所属分类: 设计模式

Feedback

# re: Observer Pattern 观察者模式 2008-07-18 11:23 Edward Li
您好,您写的文章很不错,但是我有个疑问,加入subject之后里面的obsever的add方法是哪里来的。 我个人觉得你讲的内容已经是依赖注入模式了,而observer模式仅仅是设置一个observer方法来监控某个方法状态转换的模式,很显然你讲的超出了这个范围了。总结一下就是你讲的是依赖注入+observer.不知道你怎么看这个问题。请及时回复我  回复  更多评论
  

# re: Observer Pattern 观察者模式 2008-12-25 01:13 zkenshin
看看head first design pattern会发现雷同得不得了
如果本文作者做的工作只是翻译 请说明 以免混淆  回复  更多评论
  


只有注册用户登录后才能发表评论。


网站导航: