简析设计模式之观察者模式

先看一个需求:我们要做一个气象监控软件,weatherData对象负责追踪目前的天气状况(温度、湿度、气压),另外需要分别在三个显示屏上显示目前的状况、气象统计和简单的预报,并且是需要实时更新的。

这是典型的一对多问题,而观察者模式就是定义了一系列对象之间的一对多关系,但一个对象改变状态时,其他依赖着都会收到通知,并自动更新。这非常符合我们的需求,三个显示屏甚至更多的显示屏依赖weatherData对象,当weatherData监控到的数据改变是,我们的显示屏也是会实时改变的。

在实现观察者模式之前请紧记我们的原则:针对接口编程,而不是针对实现。观察者模式的实现方式 有很多种,但下面这种是最常见的:

先定义一个主题接口:

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

每个主题有多个观察者,所以再定义一个观察者接口:

public interface Observer {
    public void update();
}

接下来就是具体主题对象和具体观察者的创建了,每个具体主题对象必须实现主题接口,每个具体观察者实现观察者接口,并且需要注册具体主题,你不注册主题又怎会给你推送新的通知呢?

显然,观察者模式很大程度地实现了对象之间的松耦合,主题只是知道观察者实现了某个接口(也就是observer接口),而观察者具体是哪个类,做了什么以及其他的一些细节,主题都不知道。而且,在任何时候我们都可以增加新的观察者,或是取代现有的观察者,哪怕是删除现有的观察者,主题都不会受到任何影响。主题做的只是发送通知给所有实现了观察者接口的对象。

松耦合的设计让对象之间的相互依赖到了最低,也就能让我们的OO系统更加弹性,可以应对更多的变化。

回到刚才的项目上,显然,我们的项目和观察者模式是相吻合的,那么我们该如何实现呢?

先定义主题接口:

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

观察者接口:

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

我们还需要一个展示的接口:

public interface DisplayElement {
    public void display();
}

好的,重点来了,看看我们的具体主题对象weatherData的实现:

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

    public WeatherData(){
        observers = new ArrayList<>();
    }
    @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(o);
        }
    }

    @Override
    public void notifyObservers() {
        for(int i = 0; i < observers.size(); i++){
            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();
    }

    //weatherData的其他方法

}

好吧,接下来就是我们的具体观察者对象了:

//只关注湿度和温度
public class CurrentConditionsDisplay implements Observer, DisplayElement {
    private float temperatur;
    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: " + temperatur + "F degress and " + humidity + "% humidity");
    }

    //所以只传温度和湿度的值
    @Override
    public void update(float temp, float humidity, float pressure) {
        this.temperatur = temp;
        this.humidity  = humidity;
        display();
    }

}

如果你还需要建立其他观察者对象也是类似的。
来个测试类:

public class Main {

    public static void main(String[] args) {
        WeatherData weatherData = new WeatherData();
        CurrentConditionsDisplay currentCondition = new CurrentConditionsDisplay(weatherData);
        weatherData.setMeasurements(80, 48, 99);
    }

}

这就是一个完整的观察者模式的样式,但是大家是否有注意到问题:主题对象总是强迫地把它的信息传送给所有的观察者,而有时候,有些观察者并不需要所有的数据,我们是不是可以主动地去拉取我们需要的数据呢?

答案是肯定的,但是这种“拉”的方式也有它的不足:每次需要数据时才去找主题对象,如果更新比较频繁的话,这种方式的缺点也是很明显的。

虽然我们已经从无到有实现了观察者模式,但是java API中已有内置实现的方式:具体主题对象继承Observable和具体观察者对象实现observer接口。我们可以修改一下之前的例子来对比一下
具体主题对象,注意Observable 要importjava .util包下的:

public class WeatherData2 extends Observable {
    private ArrayList<Observer> observers;
    private float temperature;
    private float humidity;
    private float pressure;

    public WeatherData2(){}//不需要为了记住观察者对象而建立数据结构

    //当气象站得到更新数据后,通知观察者
    public void measurementsChanged(){
        setChanged();
        notifyObservers();//调用notifyObservers之前需要先调用setChanged表示状态已改变
    }

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

    public float getTemperature() {
        return temperature;
    }
    //get方法主要是为了实现“拉”的方式
    public float getHumidity() {
        return humidity;
    }

    public float getPressure() {
        return pressure;
    }

    //weatherData的其他方法

}

具体观察者

//只关注湿度和温度
public class CurrentConditionsDisplay2 implements Observer, DisplayElement {
    private float temperatur;
    private float humidity;
    private Observable observable;

    public CurrentConditionsDisplay2(Observable observable) {
        this.observable = observable;// 保存引用是为了以后可以随时取消关注
        observable.addObserver(this);
    }

    @Override
    public void display() {
        System.out.println("Current conditions: " + temperatur + "F degress and " + humidity + "% humidity");
    }

    // 所以只传温度和湿度的值
    public void update(Observable obs, Object arg) {
        if (obs instanceof WeatherData2) {
            WeatherData2 weatherData = (WeatherData2) obs;
            this.temperatur = weatherData.getTemperature();
            this.humidity = weatherData.getHumidity();
            display();
        }
    }

}

是的,我想熟悉OO设计原则的你应该发现问题了:其一,Observable是一个类,如果我们某个类想同时具有Observable和另一个超类的行为,就会陷入两难,毕竟java不支持多重继承。其二,setChanged()方法被定义为protected,这样,除非你继承Observable,否则你无法创建Observable实例并且组合到你自己的对象来。

除非你能够扩展Observable,那么Observable可能符合你的需求,否则你还是应该要像开头那样自己实现一套完整的观察者模式。

猜你喜欢

转载自blog.csdn.net/lks1139230294/article/details/76602480