设计模式之观察者模式

本篇博客主要对设计模式中的观察者模式进行介绍

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

松耦合的威力

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

观察者模式提供了一种对象设计,让主题和观察者之间松耦合。


关于观察者的一些,主题只知道观察者实现了某个接口(也就是Observer接口)。主题不需要知道观察者的具体类是谁、做了些什么或其他任何细节。

任何时候我们都可以增加新的观察者。因为主题唯一依赖的东西是一个实现Observer接口的对象列表,所以我们可以随时增加观察者。事实上,在运行时我们可以用新的观察者取代现有的观察者,主题不会受到任何影响。同样的,也可以在任何时候删除某些观察者。

在新类型的观察者出现时,主题的代码不需要修改。假如我们有个新的具体类需要当观察者,我们不需要为了兼容新类型而修改主题的代码,所有要做的就是在新的类里实现此观察者接口,然后注册为观察者即可。主题不在乎别的,它只会发送通知给所有实现了观察者接口的对象。

我们可以独立地复用主题或观察者。如果我们在其他地方需要使用主题或观察者,可以轻易地复用,因为二者并非紧耦合。

改变主题或观察者其中一方,并不会影响另一方。因为两者是松耦合的,所以只要他们之间的接口仍被最受,我们就可以自由地改变他们。

实现气象站

气象站设计类图:
气象站设计类图
定义一个主题接口,其中接口中的registerObserver()removeObserver()这两个方法都需要一个观察者作为变量,该观察者是用来注册或被删除的。当主题状态改变时,notifyObservers()方法会被调用,以通知所有的观察者。

public interface Subject {
    void registerObserver(Observer o);
    void removeObserver(Observer o);
    void notifyObservers();
}

在观察者Observer接口中,当气象观测值改变时,主题会把这些状态值当做update()方法的参数,传递给观察者。所有的观察者都必须实现update()方法,以实现观察者接口。

public interface Observer {
    void update(float temp, float humidity, float pressure);
}

DisplayElement接口只包含了一个方法,也就是display()。当布告板需要显示时,调用此方法。

public interface DisplayElement {
    void display();
}

weatherData类实现了Subject接口,该类定义了一个observers来记录观察者,此ArrayList是在构造器中建立的;在调用registerObserver()方法注册成为观察者的时候,只要把传入的Observer对象加入到observers中即可;同样,当观察者想取消注册的时候,把它从observers中删除即可;在notifyObservers中,把主题的状态告诉了每一个观察者。因为观察者都实现了update()方法,所以知道如何通知它们;measurementsChanged()方法表示,当从气象站获取更新的观测值时,调用通知方法;setMeasurements()方法中模拟了从气象站获观测值的过程。

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

    public WeatherData() {
        observers = new ArrayList<Observer>();
    }
    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }
    @Override
    public void removeObserver(Observer o) {
        int i = observers.indexOf(o);
        if (i >= 0) {
            observers.remove(i);
        }
    }
    @Override
    public void notifyObservers() {
        for (Observer observer : observers) {
            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();
    }

    public float getTemperature() {
        return temperature;
    }

    public float getHumidity() {
        return humidity;
    }

    public float getPressure() {
        return pressure;
    }

}

目前状况布告板,此布告板实现了Observer接口,所以可以从WeatherData对象中获得改变,同时实现了DisplayElement接口用来实现显示功能;构造器需要weatherData对象作为注册之用;当update()被调用时,我们把温度和湿度保存起来,然后调用display()方法显示出来。

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);
    }
    @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 degrees and " + humidity + "% humidity");
    }
}

统计布告板

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;

    public StatisticsDisplay(WeatherData weatherData) {
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }
    @Override
    public void update(float temp, float humidity, float pressure) {
        tempSum += temp;
        numReadings++;

        if (temp > maxTemp) {
            maxTemp = temp;
        }

        if (temp < minTemp) {
            minTemp = temp;
        }

        display();
    }
    @Override
    public void display() {
        System.out.println("Avg/Max/Min temperature = " + (tempSum / numReadings)
            + "/" + maxTemp + "/" + minTemp);
    }
}

预测布告板

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);
    }
    @Override
    public void update(float temp, float humidity, float pressure) {
        lastPressure = currentPressure;
        currentPressure = pressure;

        display();
    }
    @Override
    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");
        }
    }
}

酷热指数布告板

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

    public HeatIndexDisplay(WeatherData weatherData) {
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }
    @Override
    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;
    }
    @Override
    public void display() {
        System.out.println("Heat index is " + heatIndex);
    }
}

测试四个布告板,首先建立一个WeatherData对象,建立四个布告板,并把WeatherData对象传给它们,最后调用weatherDatasetMeasurements方法模拟新的气象测量。

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

执行结果如下图。
这里写图片描述

Java内置的观察者模式

使用Java内置的观察者模式实现气象站
Java内置观察者模式实现气象站
重写WeatherData类,实现java.util.Observable类;使用Java内置观察者模式实现时,不需要追踪观察者,也不需要管理注册与删除(让超类代劳即可),所以我们把注册、添加、通知的相关代码删除;构造器不再需要为了记住观察者们而建立数据结构;在调用notifyObservers()之前,需要调用setChanged()来指示状态已经改变。

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

显示接口

public interface DisplayElement {
    public void display();
}

目前状况布告板,实现java.utilObserver接口,构造器需要一个Observable当参数,并将CurrentConditionsDisplay对象等级成为观察者;改变update()方法,增加Observable和数据对象作为参数;在update()中,先确定可观察者属于WeatherData类型,然后利用setter方法获取温度和湿度测量值,最火调用display()

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);
    }
    @Override
    public void update(Observable obs, Object arg) {
        if (obs instanceof WeatherData) {
            WeatherData weatherData = (WeatherData)obs;
            this.temperature = weatherData.getTemperature();
            this.humidity = weatherData.getHumidity();
            display();
        }
    }
    @Override
    public void display() {
        System.out.println("Current conditions: " + temperature 
            + "F degrees and " + humidity + "% humidity");
    }
}

统计布告板

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);
    }
    @Override
    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();
        }
    }
    @Override
    public void display() {
        System.out.println("Avg/Max/Min temperature = " + (tempSum / numReadings)
            + "/" + maxTemp + "/" + minTemp);
    }
}

预测布告板

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

酷热指数布告板

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);
    }
    @Override
    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();
        }
    }
    @Override
    public void display() {
        System.out.println("Heat index is " + heatIndex);
    }
}

测试布告板类

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

运行结果
Java内置实现气象站测试运行结果

java.util.Observable的黑暗面

可观察者是一个“类”而不是一个“接口”接口,更糟糕的是,它甚至没有实现一个接口。不幸的是,java.util.Observable的实现有许多问题,限制了它的使用和复用。

Observable是一个类

因为Observable是一个“类”,你必须设计一个类继承它。如果某类相同具有Observable类和另一个超类的行为,就会陷入两难,毕竟Java不支持多重继承。这限制了Observable的复用潜力。

Observable将关键的方法保护起来

如果你看看Observable API,你就会发现setChanged()方法被保护起来了(被定义成protected)。这意味着:除非你继承自Observable,否则你无法创建Observable实例并组合到你自己的对象中来。这个设计违法了第二个设计原则:“多用组合,少用继承”

观察者模式要点

  • 观察者模式定义了对象之间一对多的关系。
  • 主题(也就是可观察者)用一个共同的接口来更新观察者
  • 观察者和可观察者之间用松耦合方式结合,可观察者不知道观察者的细节,只知道观察者实现了观察者接口。
  • 使用此模式时,你可从被观察者处推(push)或拉(pull)数据(然而,推的方式被认为更“正确”)。
  • 有多个观察者时,不可以依赖特定的通知次序
  • Java有多重观察者模式的实现,包括了通用java.util.Observable。
  • 要注意java.util.Observable实现上所带来的一些问题。
  • 如果有必要的话,可以实现自己的Observable,这并不难。
  • Swing大量使用观察者模式,许多GUI框架也是如此。
  • 此模式业报应用在许多地方,例如:JavaBeans、RMI。

源码下载:https://github.com/dengqz/headfirst-designpatterns/tree/master/src/main/java/headfirst/designpatterns/observer

版权声明:本文为博主原创文章,欢迎转载,转载请注明作者;
原文超链接 :https://blog.csdn.net/java872114581/article/details/80332172

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值