JAVA设计模式--观察者模式

来源:互联网 发布:淘宝商品设置多个选项 编辑:程序博客网 时间:2024/06/05 05:31

一、由问题引入

定义:观察者模式定义了对象之间的一对多依赖,当一个对象改变状态,其他依赖者都会接到通知。

我们先不必去刻意理解这个定义,我们学习一种模式或者说他们发明一种设计模式都是为了解决某些问题,所以我们先看下问题场景,这里主要拿《Head First

设计模式》一书中举的气象站的例子来记下笔记。

需求:如图所示:


我们看下客户也就是气象站给我们提供的WeatherData类的代码结构:


我们捋顺一下我们目前知道些什么:

1、WeatherData类具有getter方法,可以取得三个测试值:温度、湿度和气压。

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

3、我们需要实现三个使用天气数据的布告板:目前状况、气象统计、天气预报

4、此系统必须可拓展,让其他开发人员建立定制的布告板,用户可以随心所欲的添加或删除任何布告板。


目前这个需求我们大体也应该明白了,先看一下错误的代码实现:

public class WeatherData {// 实例变量声明public void measurementsChanged() {float temp = getTemperature();float humidity = getHumidity();float pressure = getPressure();  currentConditionsDisplay.update(temp, humidity, pressure);  statisticsDisplay.update(temp, humidity, pressure);  forecastDisplay.update(temp, humidity, pressure);}// 这里是其他WeatherData方法}
这段代码我们或多或少都能看出些问题,首先,这段代码针对的是具体实现编程而不是针对接口编程,这导致我们如果以后再增加或删除布告板的时候还需要修改WeatherData类,另外我们观察到三个布告板都是调用update方法而且他们参数都是一致的,我们可以将这部分封装成一个接口。

二、认识观察者模式

针对这段代码的问题不多说了,我们今天学习的是观察者模式,那这个例子一定是和观察者模式有关的,接下来我们就来认识一下观察者模式:

观察者模式又被称作出版订阅模式,那我们看下出版订阅是怎么回事呢:

1、报社的业务是出版报纸

2、向某家报社订阅报纸,只要他们有新报纸出版,就会给你送来。只要你是他们的订户,你就会一直收到新报纸。

3、当你不想再看报纸的时候,取消订阅,他们就不会再送新报纸来。

4、只要报社还在运营,就会一直有人(或单位)向他们订阅报纸或取消订阅。

这个出版订阅的流程我们理解了吧,那让我们再来看一下观察者模式的定义:观察者模式定义了一系列对象之间的一对多关系,当一个对象改变状态其他依赖者都会收到通知。

定义中的"一个对象"对应的就是某个报社,"一系列对象"对应的就是多个订阅者,"状态改变"对应的就是报社出版新杂志,"收到通知"对应的就是订阅者们收到新

杂志。我们再看下观察者模式的设计类图就印象更深刻了:


三、使用观察者模式解决问题

理解了这个观察者模式的定义之后,我们发现气象站这个例子完全可以应用观察者模式来实现,既有一对多的关系又有数据更新。

WeatherData类可以作为被观察者,布告板的类可以作为观察者,所以气象站的设计实现图如下:


接下来我们就看下实现的代码吧:

1、主题接口类Subject

package headfirst.observer.WeatherStation;public interface Subject {public void registerObserver(Observer o);public void removeObserver(Observer o);public void notifyObservers();}
2、主题实现类,也就是我们前面说的那个WeatherData类
package headfirst.observer.WeatherStation;import java.util.ArrayList;public class WeatherData implements Subject {private ArrayList observers;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)observers.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();}// other WeatherData methods herepublic float getTemperature() {return temperature;}public float getHumidity() {return humidity;}public float getPressure() {return pressure;}}
3、观察者接口类

package headfirst.observer.WeatherStation;public interface Observer {public void update(float temp, float humidity, float pressure);}
4、显示布告板接口类
package headfirst.observer.WeatherStation;public interface DisplayElement {public void display();}
5、显示"目前状况"、"天气预报"和"天气统计"布告板的实现类

package headfirst.observer.WeatherStation;public class CurrentConditionsDisplay implements Observer, DisplayElement {private float temperature;private float humidity;private Subject weatherData;public CurrentConditionsDisplay(Subject weatherData) {this.weatherData = weatherData;weatherData.registerObserver(this);}public void update(float temperature, float humidity, float pressure) {this.temperature = temperature;this.humidity = humidity;display();}public void display() {System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity");}}
package headfirst.observer.WeatherStation;public class ForecastDisplay implements Observer, DisplayElement {private float currentPressure = 29.92f;  private float lastPressure;private WeatherData weatherData;public ForecastDisplay(WeatherData weatherData) {this.weatherData = weatherData;weatherData.registerObserver(this);}public void update(float temp, float humidity, float pressure) {                lastPressure = currentPressure;currentPressure = pressure;display();}public void display() {System.out.print("Forecast: ");if (currentPressure > lastPressure) {System.out.println("Improving weather on the way!");} else if (currentPressure == lastPressure) {System.out.println("More of the same");} else if (currentPressure < lastPressure) {System.out.println("Watch out for cooler, rainy weather");}}}
package headfirst.observer.WeatherStation;public class StatisticsDisplay implements Observer, DisplayElement {private float maxTemp = 0.0f;private float minTemp = 200;private float tempSum= 0.0f;private int numReadings;private WeatherData weatherData = null;public StatisticsDisplay(WeatherData weatherData) {this.weatherData = weatherData;weatherData.registerObserver(this);}public void update(float temp, float humidity, float pressure) {tempSum += temp;numReadings++;if (temp > maxTemp) {maxTemp = temp;} if (temp < minTemp) {minTemp = temp;}display();}public void display() {System.out.println("Avg/Max/Min temperature = " + (tempSum / numReadings)+ "/" + maxTemp + "/" + minTemp);}}
6、下面来看我们的测试程序代码

package headfirst.observer.WeatherStation;public class WeatherStation {public static void main(String[] args) {WeatherData weatherData = new WeatherData();//注册三个观察者CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);//气象指数的三次变化weatherData.setMeasurements(80, 65, 30.4f);weatherData.setMeasurements(82, 70, 29.2f);weatherData.setMeasurements(78, 90, 29.2f);}}
运行程序,输出如下信息:

Current conditions: 80.0F degrees and 65.0% humidityAvg/Max/Min temperature = 80.0/80.0/80.0Forecast: Improving weather on the way!Current conditions: 82.0F degrees and 70.0% humidityAvg/Max/Min temperature = 81.0/82.0/80.0Forecast: Watch out for cooler, rainy weatherCurrent conditions: 78.0F degrees and 90.0% humidityAvg/Max/Min temperature = 80.0/82.0/78.0Forecast: More of the same
至此,我们就完成了气象站交给我们的需求,但是他们刚刚来电告知,他们还需要增加一种叫做"酷热指数"的布告板,对于我们来说这个就是小事一桩了,就是增加一个酷热指数的类并让他实现Observer和DisplayElement两个接口就可以了,下面看下代码:

package headfirst.observer.WeatherStation;public class HeatIndexDisplay implements Observer, DisplayElement {float heatIndex = 0.0f;private WeatherData weatherData;public HeatIndexDisplay(WeatherData weatherData) {this.weatherData = weatherData;weatherData.registerObserver(this);}public void update(float t, float rh, float pressure) {heatIndex = computeHeatIndex(t, rh);display();}private float computeHeatIndex(float t, float rh) {float index = (float)((16.923 + (0.185212 * t) + (5.37941 * rh) - (0.100254 * t * rh) + (0.00941695 * (t * t)) + (0.00728898 * (rh * rh)) + (0.000345372 * (t * t * rh)) - (0.000814971 * (t * rh * rh)) +(0.0000102102 * (t * t * rh * rh)) - (0.000038646 * (t * t * t)) + (0.0000291583 * (rh * rh * rh)) + (0.00000142721 * (t * t * t * rh)) + (0.000000197483 * (t * rh * rh * rh)) - (0.0000000218429 * (t * t * t * rh * rh)) +0.000000000843296 * (t * t * rh * rh * rh)) -(0.0000000000481975 * (t * t * t * rh * rh * rh)));return index;}public void display() {System.out.println("Heat index is " + heatIndex);}}
再测试一下:

package headfirst.observer.WeatherStation;public class WeatherStationHeatIndex {public static void main(String[] args) {WeatherData weatherData = new WeatherData();//注册四个观察者CurrentConditionsDisplay currentDisplay = new CurrentConditionsDisplay(weatherData);StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);HeatIndexDisplay heatIndexDisplay = new HeatIndexDisplay(weatherData);//气象指数的三次变化weatherData.setMeasurements(80, 65, 30.4f);weatherData.setMeasurements(82, 70, 29.2f);weatherData.setMeasurements(78, 90, 29.2f);}}
输出如下信息

Current conditions: 80.0F degrees and 65.0% humidityAvg/Max/Min temperature = 80.0/80.0/80.0Forecast: Improving weather on the way!Heat index is 82.95535Current conditions: 82.0F degrees and 70.0% humidityAvg/Max/Min temperature = 81.0/82.0/80.0Forecast: Watch out for cooler, rainy weatherHeat index is 86.90124Current conditions: 78.0F degrees and 90.0% humidityAvg/Max/Min temperature = 80.0/82.0/78.0Forecast: More of the sameHeat index is 83.64967
四、使用Java内置的观察者模式

上面我们从无到有的完成了观察者模式的使用,其实Java API有内置的观察者模式

Java API在java.util包内有Observer和Observable两个类,分别是观察者和被观察者,看下这两个类的源代码:

Observer类:

/* * @(#)Observer.java1.20 05/11/17 * * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. */package java.util;/** * A class can implement the <code>Observer</code> interface when it * wants to be informed of changes in observable objects. * * @author  Chris Warth * @version 1.20, 11/17/05 * @see     java.util.Observable * @since   JDK1.0 */public interface Observer {    /**     * This method is called whenever the observed object is changed. An     * application calls an <tt>Observable</tt> object's     * <code>notifyObservers</code> method to have all the object's     * observers notified of the change.     *     * @param   o     the observable object.     * @param   arg   an argument passed to the <code>notifyObservers</code>     *                 method.     */    void update(Observable o, Object arg);}
Observable类:

/* * @(#)Observable.java1.39 05/11/17 * * Copyright 2006 Sun Microsystems, Inc. All rights reserved. * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. */package java.util;/** * This class represents an observable object, or "data" * in the model-view paradigm. It can be subclassed to represent an  * object that the application wants to have observed.  * <p> * An observable object can have one or more observers. An observer  * may be any object that implements interface <tt>Observer</tt>. After an  * observable instance changes, an application calling the  * <code>Observable</code>'s <code>notifyObservers</code> method   * causes all of its observers to be notified of the change by a call  * to their <code>update</code> method.  * <p> * The order in which notifications will be delivered is unspecified.   * The default implementation provided in the Observable class will * notify Observers in the order in which they registered interest, but  * subclasses may change this order, use no guaranteed order, deliver  * notifications on separate threads, or may guarantee that their * subclass follows this order, as they choose. * <p> * Note that this notification mechanism is has nothing to do with threads  * and is completely separate from the <tt>wait</tt> and <tt>notify</tt>  * mechanism of class <tt>Object</tt>. * <p> * When an observable object is newly created, its set of observers is  * empty. Two observers are considered the same if and only if the  * <tt>equals</tt> method returns true for them. * * @author  Chris Warth * @version 1.39, 11/17/05 * @see     java.util.Observable#notifyObservers() * @see     java.util.Observable#notifyObservers(java.lang.Object) * @see     java.util.Observer * @see     java.util.Observer#update(java.util.Observable, java.lang.Object) * @since   JDK1.0 */public class Observable {    private boolean changed = false;    private Vector obs;       /** Construct an Observable with zero Observers. */    public Observable() {obs = new Vector();    }    /**     * Adds an observer to the set of observers for this object, provided      * that it is not the same as some observer already in the set.      * The order in which notifications will be delivered to multiple      * observers is not specified. See the class comment.     *     * @param   o   an observer to be added.     * @throws NullPointerException   if the parameter o is null.     */    public synchronized void addObserver(Observer o) {        if (o == null)            throw new NullPointerException();if (!obs.contains(o)) {    obs.addElement(o);}    }    /**     * Deletes an observer from the set of observers of this object.      * Passing <CODE>null</CODE> to this method will have no effect.     * @param   o   the observer to be deleted.     */    public synchronized void deleteObserver(Observer o) {        obs.removeElement(o);    }    /**     * If this object has changed, as indicated by the      * <code>hasChanged</code> method, then notify all of its observers      * and then call the <code>clearChanged</code> method to      * indicate that this object has no longer changed.      * <p>     * Each observer has its <code>update</code> method called with two     * arguments: this observable object and <code>null</code>. In other      * words, this method is equivalent to:     * <blockquote><tt>     * notifyObservers(null)</tt></blockquote>     *     * @see     java.util.Observable#clearChanged()     * @see     java.util.Observable#hasChanged()     * @see     java.util.Observer#update(java.util.Observable, java.lang.Object)     */    public void notifyObservers() {notifyObservers(null);    }    /**     * If this object has changed, as indicated by the      * <code>hasChanged</code> method, then notify all of its observers      * and then call the <code>clearChanged</code> method to indicate      * that this object has no longer changed.      * <p>     * Each observer has its <code>update</code> method called with two     * arguments: this observable object and the <code>arg</code> argument.     *     * @param   arg   any object.     * @see     java.util.Observable#clearChanged()     * @see     java.util.Observable#hasChanged()     * @see     java.util.Observer#update(java.util.Observable, java.lang.Object)     */    public void notifyObservers(Object arg) {/*         * a temporary array buffer, used as a snapshot of the state of         * current Observers.         */        Object[] arrLocal;synchronized (this) {    /* We don't want the Observer doing callbacks into     * arbitrary code while holding its own Monitor.     * The code where we extract each Observable from      * the Vector and store the state of the Observer     * needs synchronization, but notifying observers     * does not (should not).  The worst result of any      * potential race-condition here is that:     * 1) a newly-added Observer will miss a     *   notification in progress     * 2) a recently unregistered Observer will be     *   wrongly notified when it doesn't care     */    if (!changed)                return;            arrLocal = obs.toArray();            clearChanged();        }        for (int i = arrLocal.length-1; i>=0; i--)            ((Observer)arrLocal[i]).update(this, arg);    }    /**     * Clears the observer list so that this object no longer has any observers.     */    public synchronized void deleteObservers() {obs.removeAllElements();    }    /**     * Marks this <tt>Observable</tt> object as having been changed; the      * <tt>hasChanged</tt> method will now return <tt>true</tt>.     */    protected synchronized void setChanged() {changed = true;    }    /**     * Indicates that this object has no longer changed, or that it has      * already notified all of its observers of its most recent change,      * so that the <tt>hasChanged</tt> method will now return <tt>false</tt>.      * This method is called automatically by the      * <code>notifyObservers</code> methods.      *     * @see     java.util.Observable#notifyObservers()     * @see     java.util.Observable#notifyObservers(java.lang.Object)     */    protected synchronized void clearChanged() {changed = false;    }    /**     * Tests if this object has changed.      *     * @return  <code>true</code> if and only if the <code>setChanged</code>      *          method has been called more recently than the      *          <code>clearChanged</code> method on this object;      *          <code>false</code> otherwise.     * @see     java.util.Observable#clearChanged()     * @see     java.util.Observable#setChanged()     */    public synchronized boolean hasChanged() {return changed;    }    /**     * Returns the number of observers of this <tt>Observable</tt> object.     *     * @return  the number of observers of this object.     */    public synchronized int countObservers() {return obs.size();    }}
对比一下使用起来跟我们的实现方式有什么区别:

首先观察者这方面它的接口跟我们的接口一样的然后说说他这个Observable类,使用这个API的时候我们把WeatherData类继承它即可,观察者对象的注册与删除修

改之类的操作他给咱们封装好了,我们就不用管了,但是区别在于他是一个类而不是一个接口,这样如果我们的WeatherData继承了Observable之后就不能继承其

他的类了,如果后续有这方面需求就会使我们的系统陷入两难境地。(少用继承,多用接口)

其次还有一个细微的差别就在于Java API中多了一个changed的变量,这个变量就是控制什么时机通知观察者的,用到了的时候再研究就可以。

五、观察者模式的"推"和"拉"

其实上面对比咱们自己实现的例子和Java API提供的观察者模式还漏掉了一个重要的差别,你们没发现Java API提供的update() 方法比咱们多了一样参数吗?这就

涉及到了观察者模式的"推"和"拉"。

第一种我们自己实现的观察者模式是将"温度"、"湿度"和"气压"三个指数都更新给观察者模式,但是每个观察者都需要这三个属性吗,又比方说,以后我们主题的

属性由3个拓展到20个,那也要将这20个值都推送给每个观察者吗?很显然,在某些情况下这样是不合理的,所以Java API提供了两种向观察者更新数据的方式:

1、第一个参数this,这个参数就是把主题对象直接发给每个观察者,那么观察者就可以通过主题对象提供的getter方法获取自己想要的值,这就是所谓的"拉"。

2、第二个参数org,这个参数是将主题的所有属性封装成一个对象更新给观察者,观察者不能选择自己想要什么,这就是"推"。

具体选择哪种方式实现就看咱们的需求了。

总结:这种模式刚刚了解,在实际中开发过程中还没使用这个模式,以后有合适的实际例子会补充上。










1 0
原创粉丝点击