设计模式2——观察者模式

1 模式说明

观察者模式就像报纸出版商和订阅者一样,在观察者模式中出版者叫做“主题”(subject),订阅者叫做“观察者”(observer)。主题内的数据产生变化时,就会通知已经订阅该主题的观察者,观察者如果订阅了主题,一旦主题发生变化发来通知,观察者就会做出相应的调整。观察者的数量可以是很多,换句话说就是很多人可以同时订阅一个主题。主题和观察者是1对多的关系。

说明
(1)如图1,一共有5个对象,分别为:Subject,Cat,Dog,Mouse,Duck。其中Subject为主题对象,代表食堂,Cat,Dog,Mouse全部订阅了Subject对象:食堂,并且同为观察者。Duck没有订阅Subject:食堂,所以当食堂发出消息:2点吃饭,Duck是接收不到的。
在这里插入图片描述

(2)如图2,Duck由于没有订阅食堂的通知,总会错过吃饭的点,饿得受不了,于是决定也注册成为观察者。于是向食堂发送注册请求。
在这里插入图片描述

如图3,食堂接受到了Duck的注册请求,于是Duck成为了观察者,食堂又发送了开饭通知:8点吃饭。此时Cat,Dog,Mouse,Duck都收到通知8点吃放,于是Duck准时吃到了饭。
在这里插入图片描述

(3)如图4,Mouse觉得食堂的饭不好吃,决定换一家(或许他发现Cat也在吃饭,就赶紧跑了),自己向食堂提出请求,不在这吃了,也就是取消订阅。
在这里插入图片描述

如图5,食堂再一次通知:12点开饭。这时就只有Duck,Cat,Dog三人得到消息。Mouse就不知道到了。
在这里插入图片描述

(4)观察者类图
如图6:Subject接口中,regitsterObserver()方法是注册观察者,removeObserver()方法是移除列表内的观察者,notifyObserver()方法是通知观察者
Observer接口是观察接口,updata()方法是更新主题变换的方法。
在这里插入图片描述

2 引例(气象监测应用)

2.1 需求

如图7,左侧4个设备,都由weather-O-Rama 提供,其中温度计,湿度计,气压计分别测试温度,湿度和气压,气象站负责收集数据,并提供接口对外提供数据。右侧的WeatherData数据对象负责从气象站获取最新的天气数据,同时向三块显示器提供数据,分别是:目前状况显示器,气象统计显示器,天气预报显示器。
在这里插入图片描述

Weather-O-Rama将他们设计的WeatherData源文件发送过来,如图8:三个getter方法分别获得当前以获取的测量值,measurementsChange()方法是测量值更新,就调用此方法。
在这里插入图片描述

2.2 分析

(1)WeatherData类具有三个getter方法,可以获取三个测量值:温度,湿度,气压
(2)当新的测量值准备好后,measurement()方法就会调用。
(3)需要实现是三个布告板:目前状况,气相统计,天气预告。一旦WeatherData有更新,布告板也要更新。
(4)需要考虑到可扩展性,未来不止这三个布告板,让其他开发人员可以自行定制布告板。

2.3 错误示例

我们可以看到,这个方法中的最后三行update()方法违法设计原则,改变的地方应该封装起来。至少应该是一个统一的接口。

public class WeatherData(){
	//实例变量声明
	public void measurement(){
		float temperature=getTemperature();
		float humidity=getHumidity();
		float pressure=getPressure();
		
		currentConditionsDisplay.update(temperature,humidity,pressure);
		statisticsDisplay.update(temperature,humidity,pressure);
		forecastDisplay.update(temperature,humidity,pressure);
	}
}

2.4 设计气象站

如图9:WeatherData类实现Subject接口,实现接口的三个方法。
三个布告板都分别实现Observer接口和DisplayElement接口。如果开发人员需要定制布告板,只需要实现Observer接口和DisplayElement接口。

在这里插入图片描述

2.5 具体代码

点击进入github源码地址>>>

(1)主题接口和WeatherData

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

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 notifyObserver() {
        for(int i=0;i<observers.size();i++){
            Observer observer= (Observer) observers.get(i);
            observer.update(temperature,humidity,pressure);
        }
    }

    public void measurementChanged(){
        notifyObserver();
    }

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

(2)观察者接口

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

(3)布告板接口和一个实现类,剩下两个自行学习解决(其实是懒了)

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

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

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

(4)测试程序:气象站

public class WeatherStation {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();
        CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData);
        weatherData.setMeasurements(25.6f, 30.5f, 33.5f);
    }
}

3 java 内置观察者模式

Java中内置了观察者模式,是在java.util包中的ObserverObservable类,java官方将主题作为类出现而不是接口。我们用java内置的观察者模式重新设计。

3.1类图

如图10,WeatherData类由于是继承了Observeable超类,所以自身不用实现registerObserver(),removeObserver()notifyObserver()三个方法。
可以看到Observeable类中还有一个setChange()方法,这是一个标记更新状态作用,让notifyObserver()知道该不该通知观察者。
在这里插入图片描述

3.2内置观察者源码实现

看一下源码实现:可以看到,使用changed成员变量控制是否通知观察者,changed的初始值是false,如果是false,notifyObservers()方法直接返回,不会通知观察者,如果是true,notifyObservers()方法才会起作用。

	private boolean changed = false;

    protected synchronized void setChanged() {
        this.changed = true;
    }

    protected synchronized void clearChanged() {
        this.changed = false;
    }

	public void notifyObservers() {
        this.notifyObservers((Object)null);
    }

    public void notifyObservers(Object var1) {
        Object[] var2;
        synchronized(this) {
            if (!this.changed) {
                return;
            }

            var2 = this.obs.toArray();
            this.clearChanged();
        }

        for(int var3 = var2.length - 1; var3 >= 0; --var3) {
            ((Observer)var2[var3]).update(this, var1);
        }

    }

3.3 内置观察者使用方法

(1) 如何把对象变为观察者
实现观察者接口Observer,然后调用任何Observeable对象的addObserver()方法,如果不想在当观察者了,调用deleteObserver()方法。

(2)可观察者(主题)如何发送通知。
第一步:调用Observeable对象的setChanged()方法。标记状态已经改变。
第二步:调用Observeable对象的两个notifyObservers()方法其中一个。

(3)观察者如何接受通知
和之前的一样,只是方法参数不同,第一个参数是标记是哪个主题,第二个参数是可观察者发送来的数据对象。

update(Observable var1, Object var2)

3.4 内置观察者代码实现

(1)WeatherData类。

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

    public WeatherData() {
    }

    public float getTemperature() {
        return temperature;
    }

    public float getHumidity() {
        return humidity;
    }

    public float getPressure() {
        return pressure;
    }

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

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

(2)CurrentConditionsDisplay类和 DisplayElement接口

public interface DisplayElement {
    void display();
}

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

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

    public void display() {
        System.out.println("Current Conditions:" + temperature + "F disgree and " + humidity + "% humidity");
    }

    public void update(Observable observable, Object o) {
        if (observable instanceof WeatherData) {
            WeatherData weatherData = (WeatherData) observable;
            this.humidity = weatherData.getHumidity();
            this.temperature = weatherData.getTemperature();
            display();
        }
    }
}

(3)WeatherStation类,测试代码

public class WeatherStation {
    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();
        CurrentConditionsDisplay currentConditionsDisplay = new CurrentConditionsDisplay(weatherData);
        weatherData.setMeasurements(25.6f, 30.5f, 33.5f);
    }
}
发布了11 篇原创文章 · 获赞 2 · 访问量 373

猜你喜欢

转载自blog.csdn.net/weixin_41938180/article/details/104139415