观察者模式

观察者模式:定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会受到通知并自动更新(采用推的模式,当然也可以采用拉的模式)

主题(Subject) + 观察者(Observer) = 观察者模式

举个栗子:

场景:一个天气气象站,根据采集到的温度、湿度、气压,会有三个不同的天气数据的布告板:1、“目前状况”布告;2、“气象统计”布告;3、“天气预报”布告。一旦WeatherData有新的测量,这些布告必须马上更新。

先看一个不好的代码示范:

WeatherData.java 类如下:

public class WeatherData {
    //实例变量声明
    public void measurementsChanged(){
        float temp = getTemperature();
        float humidity = getHumidity();
        float pressure = getPressure();
        currentConditionsDisplay.update(tem, humidity, pressure);
        statisticsDisplay.update(temp, humidity, pressure);
        forecastDisplay.update(temp, humidity, pressure);
        //这里是其他WeatherData方法
    }
}

缺点:上面是针对接口实现进行编程,而非针对接口;对于每个新的布告板,我们都得要修改代码;无法在运行时动态地增加(或删除)布告栏;布告栏没有一个共同的接口;未封装改变的部分;侵犯了WeatherData类的封装。

易知,三个布告栏的数据与天气源的三个数据温度、湿度、气压相关,也就是天气源的三个数据变化三个布告栏的数据也要跟着变化,这与观察者模式相吻合。

采用观察者模式(推荐):

主题被观察者Subject.java 类:

public interface Subject {
    public void registerObserver(Observer observer); //注册观察者
    public void removeObserver(Observer observer); //移除观察者
    public void notifyObservers(); //通知观察者
}

观察者接口Observer.java 类:

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

布告栏展示接口DisplayElement.java 类:

public interface DisplayElement {
    public void display();
}

订阅者实现类WeatherData.java 类:

public class WeatherData implements Subject {

    private ArrayList observers;
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData(){
        observers = new ArrayList();
    }

    @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(observer);
        }
    }

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

几个布告栏的实现类:

“目前状况”布告栏CurrentConditionsDisplay.java 类:

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

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

“气象统计”布告栏 StatisticsDisplay.java 类:

public class StatisticsDisplay implements  Observer, DisplayElement {

    private Subject weatherData;
    private float averageTemp = 0f;
    private float lowestTemp = 0f;
    private float highestTemp = 0f;
    private ArrayList<Float> tempList = new ArrayList<>();

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

    @Override
    public void update(float temp, float humidity, float pressure) {
        tempList.add(temp);
        this.averageTemp = this.getAverageTemp(tempList);
        this.highestTemp = Collections.max(tempList);
        this.lowestTemp = Collections.min(tempList);
        display();
    }

    @Override
    public void display() {
        System.out.println("Statistics conditions:average temperature:" + averageTemp + " F degrees and lowest temperature:" + lowestTemp + " and highest temperature:"
                + highestTemp + " F degree");
    }

    private float getAverageTemp(List<Float> tempList){
        int size = tempList.size();
        if(size <= 0){
            return 0f;
        }
        float sum = 0f;
        for(int i=0;i<size;i++){
            sum += tempList.get(i);
        }
        return sum/size;
    }
}

-----------------------------------我是分割线-------------------------------------------

实际调用WeatherStation.java 类:

public class WeatherStation {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();
        CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData);
        StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
        weatherData.setMeasurements(89, 20,33);
        weatherData.setMeasurements(100, 2,3);
    }
}

输出结果如下:

Current conditions:89.0 F degrees and 20.0%humidity

Statistics conditions:averagetemperature:89.0 F degrees and lowest temperature:89.0 and highesttemperature:89.0 F degree

Current conditions:100.0 F degrees and 2.0%humidity

Statistics conditions:averagetemperature:94.5 F degrees and lowest temperature:89.0 and highesttemperature:100.0 F degree

分析上述代码:

主题Subject.java 也即我们的订阅者接口,需要声明注册(添加)、移除、通知观察者的行为。

观察者Observer.java 都需要一个更新方法update()。

另外对于每个具体的实现者都会需要一个展示方法display(),因此增加一个DisplayElement接口。

其次,对于主题Subject的实现类WeatherData.java 中需要对温度、湿度、气压,以及一个观察者List对所有观察者进行注册和移除,当然还包括对所有观察者进行通知操作。

最后,对于所有实现Observer.java的观察者,都需要对统一的订阅者进行注册操作。

 

上述的代码,实际上采用的是推的形式进行对订阅者进行消息更新。

另外,在Java API有内置的观察者模式。Java.util包(package)内包含最基本的Observer接口与Observable类,这和我们上面的Subject接口与Observer接口很类似。Java.util包提供的Observer接口与Observable类使用上更方便,因为有许多功能已经事先准备好了。注意:你甚至可以使用推(push)或者拉(pull)的方式传送数据。

WeatherData.java 类:
public class WeatherData extends Observable {  //Observable是java.util内置的类

    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData(){
    }

    public void measurementChanged(){
        setChanged();  //这个设置决定了是推,还是拉,来进行消息通知
        notifyObservers();
    }

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

    public float getTemperature(){
        return temperature;
    }

    public float getHumidity(){
        return humidity;
    }

    public float getPressure(){
        return pressure;
    }
}

注意:setChanged()方法是Observable内置的设置是否当源数据一改变就通知订阅者进行更新,调用时即为推的模式,注释掉的话则为拉的模式。

实现类:

CurrentConditionsDisplay.java 类:

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

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

具体使用时的类如下:

WeatherStation.java 类:

public class WeatherStation {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();
        CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData);
        weatherData.setMeasurements(12, 33, 12);
        currentConditionsDisplay.update(weatherData, null);
    }
}

需要注意:实际在用观察者模式,且用到java.util内置的观察者模式,需要去查看源码是如何实现的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值