频道栏目
首页 > 程序开发 > 软件开发 > 其他 > 正文
【HeadFirst设计模式】(二)观察者模式
2016-08-29 09:20:31         来源:ChayCao  
收藏   我要投稿

气象检测应用的概况

气象检测应用由三部分组成:
1. 气象站:获取实际气象数据的物理装置
2. WeatherData对象:追踪来自气象站的数据,并更新布告板
3. 布告板:显示目前天气状况

WeatherData对象知道如何跟物理气象站联系,并取得最新的数据。
WeatherData对象会即时更新三个布告板的显示:目前状况(温度、湿度、气压)、气象统计和气象预报。

WeatherData类已经给出。我们需要编写的是measurementsChanged()方法来让布告板即时显示最新的数据。
我们先把目前知道的信息列出来,理一理:
1. WeatherData类有getter方法,获取温度、湿度、气压的测量值。
2. 当气象测量数据更新时,measurementsChanged()方法会被调用。
3. 需要实现三个布告板,“目前状况”、“气象统计”、“天气预报”,并当有数据更新时,即时更新。
4. 此系统必须可扩展,让其他开发人员建立定制的布告板,用户可以根据需要地添加或删除任何布告板。


一个错误的示范

public class WeatherData {
    public void measurementChanged() {

        //实例变量的声明

        float temp = getTemperature();
        float humidity = getHumidity();
        float pressure = getPressure();

        currentConditionsDisplay.update(temp,humidity,pressure);
        statisticsDisplay.update(temp,humidity,pressure);
        forecastDisplay.update(temp,humidity,pressure);
 
    }
    //其他方法
}

就像第一章说的,我们应该减少针对实现编程。currentConditionsDisplay、statisticsDisplay、forecastDisplay这三个实例的都具有update方法,我们可以封装成一个接口,关于布告板的实际显示,也可以封装成一个接口。


认识观察者模式

先了解下报纸的订阅是怎么回事:
1. 报社的业务是出版报纸。
2. 向某家报社订阅报纸,只要有新报纸出版,就会给你送来。只要是他们的订户,就会一直收到新报纸。
3. 在取消订阅后,就不会再送新报纸来。
4. 只要报社在运营,人们就可以向报社订阅报纸或取消订阅。

观察者模式和报纸的订阅类似,只是名称不同,我们将出版者改称为“主题”(Subject),订阅着改称为“观察者”(Observer)。

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


主题和观察者定义了一对多的关系。观察者依赖于主题,只要主题状态一有变化,观察者就会被通知。根据通知的风格,观察者可能因此新值而更新。

下面用类图表示观察者模式:


松耦合

当两个对象之间松耦合,它们依然可以交互,但是不太清楚彼此的细节。

在观察者模式中让主题和观察者之间松耦合。主题只知道观察者实现了某个Observer接口,但主题并不知道观察者的具体类是谁以及其他细节。

主题唯一依赖的东西是一个实现了Observer接口的对象列表。改变主题或观察者其中一方,并不会影响另一方。

设计原则:为了交互对象之间的松耦合而努力
松耦合可以将对象之间的相互依赖降低到最低,让我们建立具有弹性的OO系统,能够应对变化,


设计与实现气象站

\

Subject接口:

public interface Subject {
    public void registerObserver (Observer o); //用观察者作为变量,对观察者进行注册
    public void removeObserver (Observer o); //删除观察者
    public void notifyObservers (); //当主题状态改变时,通知所有观察者
}

Observer接口:

public interface Observer {
    public void update(float temp,float humidity,float pressure); //当主题状态改变时,将这些状态值作为方法参数传给观察者
}

DisplayElement接口:

public interface DisplayElement {
    public void display(); //当布告板需要显示的时候,调用此方法
}

WeatherData类:

public class WeatherData implements Subject {
    private ArrayList observers; //该ArrayList用于记录观察者
    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)observer,get(i);
            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();
    }

    //WeatherData的其他方法
}

CurrentConditionsDisplay类:

public class CurrentConditionsDisplay implements Observer, Displayment {
    private float temperature;
    private float humidity;
    private Subject weatherData; //保存对Subject的引用可以方便以后取消注册

    public CurrentConditionsDisplay (Subject weatherData) {
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }

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

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

Java内置的观察者模式

首先思考一个问题,目前是主题“推”信息给观察者,那么,观察者可以不可以从主题那里“拉”数据呢?这样可以根据观察者的需要,获得到需要的信息,而不是每次都等主题来“推”,而且需要接收“推”的所有信息。

Java内置的Observer模式对“推”、“拉”两种方式都支持。

java.util包内包含最基本的Observer接口和Observable类,与之前写的Observer接口和Subject接口相似。
 

Java内置观察者模式的运作

把对象变为观察者
实现java.util.Observer接口。
调用Observerable对象的addObserver()方法。
不再当观察者时,调用deleteObserver()。

可观察者送出通知:
继承java.util.Observable类产生。
第一步,调用setChanged()方法,标记状态已经改变的事实。
第二步,调用两种notifyObservers方法中的一个:
notifyObservers():通知观察者,但不提供数据。让观察者主动去“拉”数据。
notifyObservers(Object arg):向观察者“推”数据。

观察者接收通知
和之前的做法一样,实现update方法,但是参数有所变化。

update(Observerable o, Object arg)

主题作为一个参数,好让观察者知道是哪一个主题在通知。
Object arg则是传入notifyObservers()的数据对象。

setChanged()的必要性
如果在调用notifyObservers()之前没有先调用setChanged(),观察者就不会被通知。下面是Observerable类的伪代码:

setChanged(){
    changed = true;
}

notifyObservers(Object arg){
    if (changed) {
        for every observer on the list {
            call update (this, arg);
        }
    }
}

notifyObservers(){
    notifyObservers(null);
}

setChanged()方法让在通知观察者时,具有更多的弹性,可以适当的通知观察者。例如:气象站的数据每十分之一度就会有更新,而造成不停地通知观察者。如果希望半度以上才更新,则可以调用setChanged(),进行有效的更新。

利用内置的支持重做气象站

WeatherData改成继承java.util.Observerable

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

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

    //不再需要追踪观察者,不需要管理注册和删除(都由超类实现了)

    public WeaterData () { } //不需要为了记住观察者,而建立数据结构

    public void measurementsChanged() {
        setChanged(); //在调用notifyObservers()前需要调用setChanged()
        notifyObservers(); //没有传送数据,说明这里采用“拉”的方式
    }

    //观察者利用下面这些get方法,获得需要的信息
    public float getTemperature() {
        return temperature;
    }

    public float getHumidity() {
        return humidity;
    }

    public float getPressure() {
        return pressure;
    }
}

CurrentConditionsDisplay实现java.util.Observer

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 obserable){
        this.observable = observable;
        observable.addObservable(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 display() {
        System.out.println("Current conditions : "+temperature+"F degrees and "+humidity+"% humidity");
    }
}

观察者被通知的次序

观察者被通知的次序有java.util.Observable的notifyObservers()方法决定,所以我们不要依赖观察者被通知的次序,因为一旦观察者/可被观察者所有改变,则通知次序会发生变化。

java.util.Observable的黑暗面

Observable是一个“类”而不是一个“接口”,这样限制了它的使用与复用。
造成的问题:
如果一个类想同时继承Observable类和另一个超类的行为,便会存在问题,Java不支持多继承。
又因为没有Observable接口,所以无法建立自己的实现。Observable将关键的方法保护起来了,例如setChanged方法被定义成protected。这意味着,除非继承自Observable,否则无法创建Observable实例并组合到自己的对象中。

如果你能够扩展java.util.Observable,那么Observable“可能”可以符合你的需求。否则,你可能要像开始的做法那样自己实现一套观察者模式。

无论使用哪种方式,你都已经熟悉观察者模式了,应该能善用它们。

心得与总结

通过了解报社的机制,观察者模式是很好理解的。
先是根据主题和观察者之间应该具备的机制,我们自己编写代码实现了一套观察者模式,在编写的过程中涉及到对观察者们建立数据结构,以及对数据结构的管理。
通过观察者模式,我们也很好的理解到了两个对象之间如何松耦合。即使不清楚彼此的细节,但仍能够交互。
再后来,介绍了Java内置的观察者模式,同时介绍了“推”、“拉”两种方式,并且认识到了Observable存在的问题。

点击复制链接 与好友分享!回本站首页
上一篇:设计模式之禅——代理模式(一)普通代理&强制代理&虚拟代理&动态代理
下一篇:中介者模式
相关文章
图文推荐
文章
推荐
点击排行

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站