Java设计模式 (2)——观察者模式

最近在读《HeadFirst设计模式》,这本书口碑不错,通俗易懂读着不累,应该比GoF的那本友好一些。本篇博客(以后关于设计模式的博客也同样)是阅读此书时整理的笔记,感谢这本书的作者~

什么是观察者模式?

观察者模式定义了对象之间的一对多依赖,当一个对象改变状态时,所有依赖者都会收到通知并自动更新。观察者模式中的一对多关系
在观察者模式中,有一个主题对象,和多个观察者。主题对象一旦发生改变,就会通知所有观察者进行更新。观察者可以注销自己的观察者身份,那么便不再收到通知;非观察者也可以注册成为一个观察者。

举一个生活中的例子,观察者模式就类似于报社(主题对象)和订阅者(观察者),一旦一个人订阅了报社的报纸,就成了观察者。报社一旦有新报纸,就会发给所有订阅者。订阅者如果不想看这个报纸了,可以取消订阅,便不会再收到报纸。JDK中的监听器ActionListener和事件ActionEvent其实就是观察者模式的一个典型应用.

为什么需要观察者模式?

在一些业务场景下,需要实现类似报社-订阅者这样的功能,有多个对象需要依赖于一个对象,根据这个对象的变化进行更新。

在《Headfirst设计模式》这本书里提到的例子是:有一些测量仪器,负责测量湿度、温度之类的,一旦这些指标发生变化,多个公布板都要实时更新,这些面板包括:现状布告板、气象统计布告板、预测布告板。此外,后续会添加其他各种布告板。

现有的类是WeatherData类,该类如下图所示:
我们需要实现的就是WeatherData类的measurementsChanged方法,在这个方法里通知所有观察者,即布告板进行更新

最简单粗暴的方式就是,在measurementsChanged()里面直接挨个 panel.update(temperature, humidity, pressure),有几个布告板就update几次。

这样的问题是,当删除了某个布告板,或新增了某个布告板时,需要去改动这里的代码,说白了是面向实现编程而不是面向接口编程,而且类和类之间会互相影响,违背了设计的原则。因此,观察者模式的必要性就体现出来啦!

观察者模式的实现

接下来,就以上面所说的气象站的功能为例,用观察者模式实现他吧!

首先,我们要定义主题对象与观察者,实现了Subject接口的类是主题对象,所有实现Observer接口的类都为观察者。

观察者接口:

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

主题对象接口:

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

布告板接口:

public interface DisplayElement {
    public void display();
}

接下来实现WeatherData类:

public class WeatherData implements Subject{
    private ArrayList<Observer> observers;
    private float temprature;
    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 notifyObservers(){
        for (Observer o : observers){
            o.update(temprature, humidity, pressure);
        }
    }

    public void measurementsChanged(){
        notifyObservers();
    }

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

}

可以看到,WeatherData类内部有一个Observer数组,保存所有观察者,注册和删除观察者的功能通过增加/移除数组中的元素实现,数据更新后会立马通知观察者,调用观察者的更新方法。

接着定义布告板对象(即需要实现Observer接口和DisplayElement接口的观察者对象),只写了一个现状布告板,其他的异曲同工,没必要写了:

public class CurrentConditionDisplay implements DisplayElement, Observer {
    private float temprature;
    private float humidity;
    private float pressure;
    private Subject weatherData;

    public CurrentConditionDisplay(){
        // pass
    }

    public CurrentConditionDisplay(Subject weatherData){
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }
    
    public void setWeatherData(Subject weatherData){
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }
    
	public void removeWeatherData(Subject weatherData){
        weatherData.removeObserver(this);
        this.weatherData = null;
    }

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

    public void display(){
        System.out.println("temperature:" + temprature);
        System.out.println("humidity:" + humidity);
        System.out.println("pressure:" + pressure);

    }
}

注意,在书中,直接将WeatherData对象作为这里构造函数的参数,构造时直接定义了对象所依赖的主题对象,我觉得不是很灵活,所以稍微做了一些改动,增添了setWeatherData()方法进行设置,而构造函数则增加了一个缺省的空构造函数。此外增加了一个删除Subject的方法removeWeatherData()。

运行程序进行测试:

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

        weatherData.setMeasurements(30, 65,34.5f);

        weatherData.setMeasurements(20, 65,35.5f);
    }
}

结果如下:

以上就是自己手动用观察者模式实现气象站功能的代码。事实上,Java内置了观察者模式,通过导入java.util包里的Observer接口和Observable类就可以实现,非常方便而且功能更多。

Java内置的观察者模式用法:

  • 把对象变成观察者:实现java.util.Observer接口,调用Observable对象的addObserver()方法。
  • 主题对象进行通知:继承java.util.Observable类,调用setChanged(), 调用notifyObservers()。setChanged方法把boolean变量changed由false改为true, notify方法会调用所有观察者的update方法,通知完毕时将changed改回false。setChanged
  • 观察者接收通知:update(Observable o, Object args),args是notify的时候传的数据

接下来,用Java内置的观察者模式重写气象站:

首先是WeatherData类:

import java.util.Observable;

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

    public WeatherData(){ }


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

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

    public float getHumidity() {
        return humidity;
    }

    public float getPressure() {
        return pressure;
    }

    public float getTemprature() {
        return temprature;
    }
}

以及CurrentConditionDisplay类:

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

public class CurrentConditionDisplay implements DisplayElement, Observer {
    private float temprature;
    private float humidity;
    private float pressure;
    private Observable weatherData;

    public CurrentConditionDisplay(){
        // pass
    }

    public CurrentConditionDisplay(Observable weatherData){
        this.weatherData = weatherData;
        weatherData.addObserver(this);
    }


    public void update(Observable o, Object arg){
        if (o instanceof WeatherData){
            WeatherData weatherData = (WeatherData)o;
            this.temprature = weatherData.getTemprature();
            this.humidity = weatherData.getHumidity();
            this.pressure = weatherData.getPressure();
            display();
        }
    }

    public void display(){
        System.out.println("temperature:" + temprature);
        System.out.println("humidity:" + humidity);
        System.out.println("pressure:" + pressure);

    }

}

事实上,Java中的Observable类存在一些问题,主要表现在:
① 他是一个类,不是接口。如果一个类继承了一个父类,又想继承Observable类,是无法实现的。
② 如①所说,既然不能继承,那么尝试用组合的方式呢?也是不行的,因为Observable类中的setChanged方法是一个protected方法,只有子类能调用,所以组合也是不行的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值