《Head First 设计模式》模式2——观察者模式

概念什么的都是正确的废话!

所以不说废话,直接上栗子:


工作合约

公司中标了一个项目,要为Weather-O-Rama气象站建立下一代气象观测站!
要求:

  • 建立一个应用,有三个布告板,分别显示目前状况、气象统计、天气预报
  • 一旦气象站有新的测量数据,必须立即更新布告板
  • 系统可扩展,可以随时添加或移除定制的各种布告板

负责跟踪目前的温度、湿度、气压的对象WeatherData由Weather-O-Rama提供:

public class WeatherData {
    public void measurementsChanged() {
        // 当有新数据时,该方法被调用
    }

    // 温度
    public float getTemperature() {
    }
    // 湿度
    public float getHumidity() {
    }
    // 气压
    public float getPressure() {
    }
}

如何使用呢?先看个错误示范:

public void measurementsChanged() {
    float temp = getTemperature();
    float humidity = getHumidity();
    float pressure = getPressure();

    currentConditionsDisplay.update(temp, humidity, pressure);
    statisticsDisplay.update(temp, humidity, pressure);
    forcastsDisplay.update(temp, humidity, pressure);
}

回想策略模式里提到的3个设计原则,上面measurementsChanged()的实现有什么问题?


从订阅报纸开始

我们知道,用户订阅报纸的流程是这样的:

  1. 只要用户订阅了报社的报纸,那么报社有新报纸出版时用户就会收到新报纸;
  2. 当用户不想再看报纸时,可以取消订阅;
  3. 只要报社还在运营,就会一直有用户向他们订阅或取消报纸。

为了更好地理解观察者模式,我们把出版社称为“主题”(Subject),把订阅者称为“观察者”(Observer)。
从订阅报纸过渡,我们可以初步设计出这样的类结构:
观察者模式类图

从上面的设计可以看到,虽然SubjectObserver是两个不同的对象,却依然可以交互,且无需清楚彼此的细节。
Subject的角度看:其唯一依赖的是一个实现Observer接口的对象列表,所以可以随时增加或移除Observer
Observer的角度看:出现新类型的Observer时,只需实现Observer接口并向Subject注册即可,Subject不必做修改。

如此一来,改变SubjectObserver其中一方,都不会影响到另一方。因为两者是 松耦合 的,只要它们之间的接口仍被遵守,我们就可以自由地改变它们。

此处引出第4个设计原则

为了交互对象之间的松耦合设计而努力。

前3个设计原则请见策略模式

设计气象站

从上面的“从订阅报纸开始”得到启发后,我们尝试修改一开始糟糕的设计,重新设计气象站,类图如下:
气象站类图

首先创建ObserverSubject接口。另,因布告板的显示动作是不同于观察者的更新动作的,应该设计一个接口用于做显示气象信息的动作,于是再加一个Displayment接口:

public interface Observer {
    void update(float temp, float humidity, float pressure);
}
public interface Subject {
    void registerObserver(Observer observer);
    void removeObserver(Observer observer);
    void notifyObserver();
}
public interface DisplayElement {
    void display();
}

然后,WeatherData实现Subject接口:

public class WeatherData implements Subject{    
    private List<Observer> observers;
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData() {
        observers = new ArrayList<Observer>();
    }

    @Override
    public void registerObserver(Observer observer) {
        observers.add(observer);
    }

    @Override
    public void removeObserver(Observer observer) {
        int i = observers.indexOf(observer);
        if (i > 0) {
            observers.remove(i);
        }
    }

    @Override
    public void notifyObserver() {
        for (Observer observer : observers) {
            observer.update(temperature, humidity, pressure);
        }
    }

    public void measurementsChanged() {
        notifyObserver();
    }

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


    // 温度
    public float getTemperature() {
        return temperature;
    }
    // 湿度
    public float getHumidity() {
        return humidity;
    }
    // 气压
    public float getPressure() {
        return pressure;
    }
}

CurrentConditionsDisplay布告板为例,创建一个观察者:

// 该布告板显示气象站当前的观测值
public class CurrentConditionsDisplay implements Observer, DisplayElement {
    private float temperature;
    private float humidity;
    private Subject weatherData;

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

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

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

}

启动气象站,测试程序:
(PS:StatisticsDisplayForecastDisplay是另外两个布告板,具体实现可以自行定制~)

public class WeatherStation {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();

        Observer currentDisplay = new CurrentConditionsDisplay(weatherData);
        Observer statisticsDisplay = new StatisticsDisplay(weatherData);
        Observer forecastDisplay = new ForecastDisplay(weatherData);

        weatherData.setMeasurements(80, 65, 30.4f);
        System.out.println();
        weatherData.setMeasurements(82, 70, 29.2f);
        System.out.println();
        weatherData.setMeasurements(78, 90, 29.2f);
        System.out.println();
    }
}

输出结果:

Current conditions: 80.0F degress and 65.0% humidity
Avg/Max/Min temperature = 80.0/80.0/80.0
Forecast: Improving weather on the way!

Current conditions: 82.0F degress and 70.0% humidity
Avg/Max/Min temperature = 81.0/82.0/80.0
Forecast: Watch out for cooler, rainy weather

Current conditions: 78.0F degress and 90.0% humidity
Avg/Max/Min temperature = 80.0/82.0/78.0
Forecast: More of the same

至此,大家应该知道如何使用观察者模式了吧!

Java内置的观察者模式

由于观察者种类很多,主题不能预料到每个观察者的需求,如果每次主题都有更新就通知观察者就太频繁了,比如气象站的数据一有微小的改变就会更新。。。还有一点是观察者可能不需要知道关于主题的所有状态,它们只要获取到自己需要的数据就行了。

这种情况可以改进吗?以前是主题主动push(推送)消息给观察者,可不可以主题在控制好通知频率的情况下,让观察者收到通知后从主题pull(拉取)它们所需的数据呢?

幸运的时,JDK1.0就已内置了观察者模式——提供java.util.Observablejava.util.Observer,下面是Observable的关键代码:

public class Observable {
    private boolean changed = false;
    private Vector obs;

    /** Construct an Observable with zero Observers. */

    public Observable() {
        obs = new Vector();
    }

    public synchronized void addObserver(Observer o) {
        if (o == null)
            throw new NullPointerException();
        if (!obs.contains(o)) {
            obs.addElement(o);
        }
    }

    public synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }

    public void notifyObservers() {
        notifyObservers(null);
    }

    public void notifyObservers(Object arg) {
        Object[] arrLocal;

        synchronized (this) {
            if (!changed)
                return;
            arrLocal = obs.toArray();
            clearChanged();
        }

        for (int i = arrLocal.length-1; i>=0; i--)
            ((Observer)arrLocal[i]).update(this, arg);
    }

    protected synchronized void setChanged() {
        changed = true;
    }

}

从源码可以看到,Observable通知观察者时调用notifyObservers(),会先判断changed是否为真,为真才通知所有观察者。所以Observable的子类确定要通知观察者时,要先调用父类ObservablesetChanged(),这里有个好处就是Observable的子类可以控制何时通知观察者,而不用一旦有微小的改变就立刻进行通知。

但是!!!java.util.Observable是存在一些缺陷的:
1、Observable是一个类而不是接口,更糟的是它没有实现一个接口,使用者必须设计一个类继承它。如果某类想同时具有Observable类和另一个超类的行为,就会陷入两难——因为java不支持多继承。这就限制了Observable的复用能力。
2、setChanged()是protected的——除非继承Observable,否则使用者无法创建Observable实例组合到自己的对象中去。这违反了第2个设计原则:“多用组合,少用继承”

当然,如果java.util.Observable的缺陷不会影响你的使用,那直接利用它最方便不过了。不过其实自己实现一套观察者模式也不难~

Swing中的观察者模式

玩过Swing的同学应该知道Swing中大量使用了观察者模式,通过监听器监听控件的各种事件。这里也放个小栗子让大伙感受下:

public class SwingObserverExample {
    JFrame frame;

    public static void main(String[] args) {
        SwingObserverExample example = new SwingObserverExample();
        example.go();
    }

    public void go() {
        frame = new JFrame();

        JButton button = new JButton("Should I do it?");
        button.addActionListener(new AngelListener());
        button.addActionListener(new DevilListener());
        frame.getContentPane().add(BorderLayout.CENTER, button);

        // Set frame properties 
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(BorderLayout.CENTER, button);
        frame.setSize(300,300);
        frame.setVisible(true);
    }

    class AngelListener implements ActionListener {
        public void actionPerformed(ActionEvent event) {
            System.out.println("Don't do it, you might regret it!");
        }
    }

    class DevilListener implements ActionListener {
        public void actionPerformed(ActionEvent event) {
            System.out.println("Come on, do it!");
        }
    }
}

观察者模式

吃完栗子可以讲正确的废话了!

观察者模式的定义:

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值