观察者模式

本文深入解析观察者模式的核心概念及其实现方式,介绍其在Java中的应用案例,包括天气站类图示例与Swing监听器的应用。文章还探讨了Java内置Observable类的局限性及其替代方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 观察者模式: 定义了对象之间的一对多依赖,当一个对象改变状态是,它的所有依赖着都会受到通知并自动更新;
2. 主题和观察者定义了一对多对象。观察者依赖于此主题,只要主题一有变化,观察者就会被通知。根据通知的风格,观察者可能因此新值而更新;
3. 观察者模式的类图:

这里写图片描述

4. 松耦合:当两个对象之间松耦合,它们依然可以交互,但是不太清楚彼此的细节;
5. 观察者模式提供了一种对象设计,让主题和观察者之间松耦合;
    a. 主题只知道观察者实现了某个接口,也就是Observer接口,主题不需要知道观察者的具体类是谁、做了些什么或其他任何细节;
    b. 任何时候都可以增加新的观察者。因为主题唯一依赖的东西是一个实现Observer接口的对象列表,所以我们可以随时增加观察者。事实上,在运行时我们可以用新的观察者取代现有的观察者,主题不会受到任何影响。同样的,也可以在任何时候删除某些观察者。
    c. 当新类型的观察者出现时,主题的代码不需要修改。假如有个新的具体类需要当观察者,我们不需要为了兼容新类型而修改主题的代码,所有要做的就是在新的类里实现此观察者接口,然后注册为观察者即可。主题不在乎别的,它只会发送通知给所有实现了观察者接口的对象。
    d. 我们可以独立地复用主题或观察者。如果我们在其他地方需要使用主题或观察者,可以轻易地复用,因为二者并非紧耦合;
    e. 改变主题或观察者其中一方,并不会影响另一方。因为两者是松耦合的,所以只要他们之间的接口仍被遵守,我们就可以自由地改变他们。
6. 设计原则4:为了交互对象之间的松耦合设计而努力;
    a. 松耦合的设计之所以能让我们建立有弹性的OO系统,能够应对变化,是因为对象之间的互相依赖降到了最低;
7. 气象站类图:

这里写图片描述

8. 参考Java JDK中的两种Observer设计模式:一种是“推”的方式,一种是“拉”的方式;
    a. 将对象变成观察者:
        i. 实现观察者接口(java.util.Observer),然后调用任何Observable对象的addObserver()方法。不想再当观察者时,调用deleteObserver()方法就可以了。
    b. 可观察者要如何送出通知?
        i. 首先,利用扩展java.util.Observer接口产生“可观察者”类,然后,需要两个步骤:
            1) 先调用setChanged()方法,标记状态已经改变;
            2) 然后调用两种notifyObserver()方法中的一个:notifyObservers()或notifyObservers(Object arg)
        ii. 观察者如何接受通知?
            1) 观察者实现了更新的方法,但是方法的签名不太一样:update(Observable o, Object arg);
            2) 第一个参数:主题本身当作一个变量,好让观察者知道是哪个主题;
            3) 第二个参数:这是传入notifyObservers()的数据对象,如果没有,则为空;
        iii. 如果想“推”数据给观察者,可以吧数据当作数据对象传递给notifyObservers(arg)方法。否则,观察者就必须从可观察者对象中“拉”(pull)数据。
        iv. Pull方式:

这里写图片描述

        v. Push方式:
            1) WeatherData.java:
import java.util.Observable;

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

                        public WeatherData() { }

                        public void measurementsChanged() {
                                setChanged();
                                notifyObservers();
                        }

                        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;
                        }
                }
            2) CurrentConditionsDisplay.java
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 observable) {
                                this.observable = observable;
                                observable.addObserver(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 void display() {
                                System.out.println("Current conditions: " + temperature 
                                        + "F degrees and " + humidity + "% humidity");
                        }
                }
9. 不要依赖与观察者被通知的次序
    a. Java.util.Observable实现了notifyObserver()方法,这导致了通知观察者的次序不同于我们先前的次序;
    b. 如果我们的代码依赖这样的次序,就是错的。因为一旦观察者/可观察者的实现有所改变,通知次序就会改变,很可能就会产生错误的结果。这绝对不是我们所认为的松耦合。
10. Java.util.Observable的黑暗面
    a. Observable是一个类
        i. Observable是一个“类”,必须设计一个类继承它。如果某类想同时具有Observable和另一个超类的行为,就会陷入两难,Java不支持多重继承,这限制了Observable的复用潜力;
        ii. 因为没有Observable接口,所以无法建立自己的实现,和Java内置的Observer API搭配使用,也无法将java.util的实现换成另一套做法的实现。
    b. Observable将关键的方法保护起来
        i. Observable API中,setChanged()方法被保护起来了,被定义成protected,这意味着除非你继承自Observable,否则你无法创建Observable实例并组合到你自己的对象中来。这个设计违反了第二个设计原则:“多用组合,少用继承”;
    c. 解决的办法:自己实现这一套观察者模式;
11. JDK中观察者模式的其他应用:Swing API:JButton;
    a. JButton的超类AbstractButton,有许多增加与删除倾听者(listener)方法,这些方法可以让观察者感应到Swing组件的不同类型时间。比如,ActionListener让你“倾听”可能发生在按钮上的动作,例如按下按钮。你可以在Swing API中找到许多不同类型的倾听者;
12. Summarize the key points:
    a. 观察者模式定义了对象之间一对多的关系;
    b. 主题(可观察者)用一个共同的接口来更新观察者;
    c. 观察者和可观察者之间用松耦合方式结合(loosecoupling),可观察者不知道观察者的细节,只知道观察者实现了观察者接口;
    d. 使用此模式时,你可从被观察者出推(push)或拉(pull)数据,但推的方式被认为更“准确”;
    e. 有多个观察者时,不可以依赖特定的通知次序;
    f. Java有多种观察者模式的实现,包括了通用的java.util.Observable。但要注意java.util.Observable实现上锁带来的一些问题;
    g. 如果有必须,应该实现自己的观察者模式;
    h. Swing大量使用观察者模式,许多GUI框架也是类似;
    i. 应用此模式的许多地方:JavaBeans、RMI;
13. 几个设计原则在观察者模式中的体现:
    a. 设计原则1:找出应用中可能需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起
        i. 在观察者模式中,会改变的是主题的状态,以及观察者的数据和类型。用这个模式,你可以改变依赖于主题的对象,却不必改变主题。
    b. 设计原则2: 针对接口编程,而不是针对实现编程
        i. 主题与观察者都使用接口:观察者利用主题的接口向主题注册,而主题利用观察者接口通知观察者。这样可以让两者之间运作正常,又同时具有松耦合;
    c. 设计原则3:多用组合,少用继承
        i. 观察者模式利用“组合”将许多观察者组合进主题中。对象之间的这种关系不是通过继承产生的,而是在运行时利用组合的方式而产生的。
14. 源代码:
CurrentConditionsDisplay.java
package headfirst.designpatterns.observer.weatherobservable;

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

DisplayElement.java

package headfirst.designpatterns.observer.weatherobservable;

public interface DisplayElement {
    public void display();
}

ForecastDisplay.java

package headfirst.designpatterns.observer.weatherobservable;

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

public class ForecastDisplay implements Observer, DisplayElement {
    private float currentPressure = 29.92f;  
    private float lastPressure;

    public ForecastDisplay(Observable observable) {
        observable.addObserver(this);
    }

    public void update(Observable observable, Object arg) {
        if (observable instanceof WeatherData) {
            WeatherData weatherData = (WeatherData)observable;
            lastPressure = currentPressure;
            currentPressure = weatherData.getPressure();
            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");
        }
    }
}

HeatIndexDisplay.java

package headfirst.designpatterns.observer.weatherobservable;

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

public class HeatIndexDisplay implements Observer, DisplayElement {
    float heatIndex = 0.0f;

    public HeatIndexDisplay(Observable observable) {
        observable.addObserver(this);
    }

    public void update(Observable observable, Object arg) {
        if (observable instanceof WeatherData) {
            WeatherData weatherData = (WeatherData)observable;
            float t = weatherData.getTemperature();
            float rh = weatherData.getHumidity();
            heatIndex = (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)));
            display();
        }
    }

    public void display() {
        System.out.println("Heat index is " + heatIndex);
    }
}

StatisticsDisplay.java

package headfirst.designpatterns.observer.weatherobservable;

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

public class StatisticsDisplay implements Observer, DisplayElement {
    private float maxTemp = 0.0f;
    private float minTemp = 200;
    private float tempSum= 0.0f;
    private int numReadings;

    public StatisticsDisplay(Observable observable) {
        observable.addObserver(this);
    }

    public void update(Observable observable, Object arg) {
        if (observable instanceof WeatherData) {
            WeatherData weatherData = (WeatherData)observable;
            float temp = weatherData.getTemperature();
            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);
    }
}

WeatherData.java

package headfirst.designpatterns.observer.weatherobservable;

import java.util.Observable;

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

    public WeatherData() { }

    public void measurementsChanged() {
        setChanged();
        notifyObservers();
    }

    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;
    }
}

WeatherStation.java

package headfirst.designpatterns.observer.weatherobservable;

public class WeatherStation {

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

        CurrentConditionsDisplay currentConditions = 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);
    }
}

WeatherStationHeatIndex.java

package headfirst.designpatterns.observer.weatherobservable;

public class WeatherStationHeatIndex {

    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();
        CurrentConditionsDisplay currentConditions = 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);
    }
}

SwingObserverExample.java

package headfirst.designpatterns.observer.swing;

import java.awt.*;
import javax.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?");

        // Without lambdas
        //button.addActionListener(new AngelListener());
        //button.addActionListener(new DevilListener());

        // With lambdas
        button.addActionListener(event -> 
            System.out.println("Don't do it, you might regret it!")
        );
        button.addActionListener(event ->
            System.out.println("Come on, do it!")
        );
        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);
    }

    /*
     * Remove these two inner classes to use lambda expressions instead.
     * 
    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!");
        }
    }
    */

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值