设计模式:观察者模式

观察者模式是一种对象行为模式,当一个对象状态改变时,所有依赖它的对象都会得到通知并自动更新。本文通过气象监测系统举例,解释了如何实现观察者模式,并对比了Java内置的观察者API。讨论了其优缺点和适用场景,强调了在选择使用内置API或自定义实现时应考虑的权衡。
摘要由CSDN通过智能技术生成

1. 观察者模式

观察者模式(Observer)是一种对象行为模式。它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。比如关注某博主,当博主发布新文章时,就会通知你;在邮箱中也可以订阅某些文章,文章更新时会发邮件给你等。

观察者模式又可称为发布-订阅模式(Publish/Subscribe)。观察者的结构由两部分组成,分别为Subject和Observer,UML图如下:

在这里插入图片描述

2. 实现

想来想去还是拿《HeadFirst设计模式》中观察者章节的气象监测例子:此系统由三个部分组成:气象站(获取实际气象数据的物理装置)、WeatherData对象(追踪来自气象站的数据,并更新布告板)和布告板(显示目前天气状况给用户看)。

WeatherData对象知道如何跟物理气象站联系,以取得更新的数据。WeatherData对象会随即更新三个布告板的显示:目前状况(温度、湿度、气压)、气象统计和天气预报。“目前状况”是三种显示之一,用户也可以获得气象统计与天气预报。现在利用WeatherData对象取得数据,并更新三个布告板:目前状况、气象统计和天气预报。


Subject:

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

Observer:

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

DisplayElement:因为每个布告板都需要显示数据,所以单独定义一个接口

public interface DisplayElement {
    void display();
}

Weather:

import java.util.ArrayList;

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

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

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

    @Override
    public void removerObserver(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 = observers.get(i);
            observer.update(temperature, humidity, pressure);
        }
    }

    // 模拟设置数据
    public void setMeasurements(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        notifyObservers();
    }
}

CurrentConditionsDisplay:目前气温状况的面板

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

ForecastDisplay:预测天气湿度的面版

public class ForecastDisplay implements Observer, DisplayElement {

    private float currentPressure = 30.0f; // 假设的
    private float lastPressure; // 记录上一次的气压

    private Subject weatherData;

    public ForecastDisplay(Subject weatherData) {
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }
    
    @Override
    public void update(float temperature, 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");
        }
    }
}

MeteorologicalStatisticsDisplay:温度统计的面板

public class MeteorologicalStatisticsDisplay implements Observer, DisplayElement {

    private float tempSum = 0.0f;
    private float minTemp = 200.0f;
    private float maxTemp = 0.0f;
    private int count; // 记录了多少次温度

    private Subject weatherData;

    public MeteorologicalStatisticsDisplay(Subject weatherData) {
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }
    
    @Override
    public void update(float temperature, float humidity, float pressure) {
        tempSum += temperature;
        count++;

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

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

        display();
    }

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

以后如果有新需求,需要添加新的布告板也是很容易的,符合开-闭原则。

WeatherStation:客户端

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

        CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData);
        MeteorologicalStatisticsDisplay meteorologicalStatisticsDisplay = new MeteorologicalStatisticsDisplay(weatherData);
        ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);

        weatherData.setMeasurements(38, 35, 30.4f);
        weatherData.setMeasurements(29, 56, 29.5f);
    }
}

输出:

在这里插入图片描述

3. Java内置观察者

Java中也有提供观察者模式的API给我们用。在java.util包(package)内包含最基本的Observable类与Observer接口,这和上面的Subject接口与Observer接口很相似。

Observable类:等价于上面的Subject。

  • Observable():带有一个无参构造方法。
  • addObserver(Observer o):跟上面的registerObserver(Observer observer)一样。
  • deleteObserver(Observer o):跟上面的removerObserver(Observer observer)一样。
  • notifyObservers():跟上面的notifyObservers()一样。
  • notifyObservers(Object arg):这个的功能也是通知,但是可以传数据进去。
  • deleteObservers():删除所有观察者对象。
  • setChanged():该方法中设置一个标记changed为true。功能是:如果Observable中的数据发生变化时,则调用该方法。当要通知观察者时,会判断changed是否为true,如果不为true说明数据无变动,那么也没必要通知观察者们。
  • clearChanged():把标记changed变为默认值(false)。
  • hasChanged():返回changed的值。

Observer接口:等价于上面的Observer,但是update方法的参数不同。

  • update(Observable o, Object arg):每次更新需要传入对应的Observable ,然后传参数。

使用:

  1. 如何把对象变成观察者?实现观察者接口(java.uitl.Observer),然后调用任何Observable对象的addObserver()方法。不想再当观察者时,调用deleteObserver()方法就可以了。

  2. 可观察者要如何送出通知?首先,你需要利用扩展java.util.Observable接口产生“可观察者”类,然后,需要两
    个步骤:

    • 先调用setChanged()方法,标记状态已经改变的事实。

    • 然后调用两种notifyObservers()方法中的一个,具体看情况是否传值。

  3. 观察者如何接收通知?如果想“推”(push)数据给观察者,可以把数据当作数据对象传送给notifyObservers(arg)方法。否则,观察者就必须从可观察者对象中“拉”(pull)数据。


来改下上面的例子:

首先来改WeatherData,只需要继承Observable类即可,里面的其他方法都不需要我们写了,在子类可以添加其他数据:

import java.util.Observable;

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


    // 模拟设置
    public void setMeasurements(float temperature, float humidity, float pressure) {
        this.temperature = temperature;
        this.humidity = humidity;
        this.pressure = pressure;
        measurementsChanged();
    }

    // 封装通知
    public void measurementsChanged() {
        // 每次变化需要调用setChanged
        setChanged();
        // notifyObservers方法中会自动调用clearChanged,把changed标记重置
        notifyObservers();
    }

    public float getTemperature() {
        return temperature;
    }

    public float getHumidity() {
        return humidity;
    }

    public float getPressure() {
        return pressure;
    }
}

修改CurrentConditionsDisplay

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

public class CurrentConditionsDisplay implements Observer, DisplayElement {
    private float temperature;
    private float humidity;
	
    private Observable observable;

    public CurrentConditionsDisplay(Observable observable) {
        this.observable = observable;
        observable.addObserver(this);
    }
    
    @Override
    public void update(Observable o, Object arg) {
        if(o instanceof WeatherData) {
            WeatherData weatherData = (WeatherData)o;
            this.temperature = weatherData.getTemperature();
            this.humidity = weatherData.getHumidity();
            display();
        }
    }
    
    @Override
    public void display() {
        System.out.println("Current conditions: " + temperature
            + "F degrees and " + humidity + "% humidity");
    }
    
}

修改MeteorologicalStatisticsDisplay:

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

public class MeteorologicalStatisticsDisplay implements Observer, DisplayElement {

    private float tempSum = 0.0f;
    private float minTemp = 200.0f;
    private float maxTemp = 0.0f;
    private int count; // 记录了多少次温度
    
    private Observable observable;

    public MeteorologicalStatisticsDisplay(Observable observable) {
        this.observable = observable;
        observable.addObserver(this);
    }
    
    @Override
    public void update(Observable o, Object arg) {
        if(o instanceof WeatherData) {
            WeatherData weatherData = (WeatherData)o;
            float temperature = weatherData.getTemperature();
            tempSum += temperature;
            count++;

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

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

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

}

修改ForecastDisplay

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

public class ForecastDisplay implements Observer, DisplayElement {

    private float currentPressure = 30.0f; // 假设的
    private float lastPressure; // 记录上一次的气压

    private Observable observable;

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

    @Override
    public void update(Observable o, Object arg) {
        if(o instaWnceof WeatherData) {
            WeatherData weatherData = (WeatherData)o;
            // 记录上一次的气压
            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");
        }
    }

}

测试的代码跟上面一样,不用改动即可跑,结果一样。

但是java内置的观察者模式API其实也有缺点:需要注意

  • 就是Observable居然是类,如果WeatherData同时还要继承别的类这就不能用了,因为Java中不允许多继承。而且也违反了 “针对接口编程,而非针对实现编程 ” 的原则。
  • 可以看下Observable的源码,会发现setChanged()方法的权限是protected,这就表示只能继承Observable来使用它,而不能创建Observable的实例组合到其他类中,这违反了“多用组合,少用继承”的原则。

所以看具体需求吧,如果确定了现在和未来某个类就只会继承一个超类,那么就可以使用Observable,否则就自己写一个咯。

4. 总结

下面转自菜鸟教程:

意图: 定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。

主要解决: 一个对象状态改变给其他对象通知的问题,而且要考虑到易用和低耦合,保证高度的协作。

何时使用: 一个对象(目标对象)的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,进行广播通知。

优点

​ 1、观察者和被观察者是抽象耦合的。

​ 2、建立一套触发机制。

缺点:

​ 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。

​ 2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用(死循环),可能导致系统崩溃。

​ 3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。

使用场景:

  • 一个抽象模型有两个方面,其中一个方面依赖于另一个方面。将这些方面封装在独立的对象中使它们可以各自独立地改变和复用。
  • 一个对象的改变将导致其他一个或多个对象也发生改变,而不知道具体有多少对象将发生改变,可以降低对象之间的耦合度。
  • 一个对象必须通知其他对象,而并不知道这些对象是谁。
  • 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。

参考:

《HeadFirst设计模式》

菜鸟教程:观察者模式

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值