设计模式之观察者模式

        当对象间存在一对多关系时,则使用观察者模式(Observer Pattern)。比如,当一个对象被修改时,则会自动通知它的依赖对象。观察者模式属于行为型模式。

        假如现在需要实现一个天气预报的服务,气象站可以用提供一些服务,例如温度、湿度和气压。因为这些数据的获取需要建立很多的观测站,投入很大,而各个应用想获取天气情况的时候没有必要自己建造,只需要从气象站提供的接口获取就可以。假设应用A 需要展示当前的天气情况,应用B需要展示天气情况的历史统计,应用C需要展示未来天气预报。可以用以下的图来表示这个情况:


        如果按照气象站给的接口,我们可以进行一个简单的实现。

public class WeatherData{
	
	public void measurementsChanged(){
		// 当气象变化时,获取最新的气象情况并记录
		float temp = getTemperature();
		float humidity = getHumidity();
		float pressure = getPressure();
		//更新应用展示的数据
		currentConditionsDisplay.update(temp, humidity, pressure);
		statisticsDisplay.update(temp, humidity, pressure);
		forecastDisplay.update(temp, humidity, pressure);
	
	}
}

上边的代码实现,虽然能满足现在的要求,但是还是存在以下几个问题:

1.针对的是具体实现编程而不是针对接口。

2.无法在运行时动态地增加或删除应用。

3.变化的部分没有和不变的部分分开(请参照策略模式)。

那什么是观察者模式呢?举个例子,我们使用微信公众号的模式,就是一个典型的观察者模式。简而言之,微信公众号 + 用户 = 观察者模式。在观察者模式中,公众号被叫做“主题”(Subject),用户被称之为“观察者”(Obsever)。观察者模式定义了对象之间的一对多依赖,这样一来,当一个对象状态改变时,它的所有依赖对象都会收到通知并自动跟新。观察者模式定义了一种对象设计,让主题和观察者之间松耦合。当两个对象之间松耦合,它们之间依然可以交互,但是不太清楚彼此之间的细节。下面是观察者模式的类图:


松耦合的设计之所以能够让我们建立有弹性的OO系统,能够应对变化,是因为对象之间的依赖降到了最低。所以我们的设计原则之一就是为了交互对象之间的松耦合而努力。根据上边的类图以及描述,我们可以自己编写一个案例的实现,虽然Java内置了对观察者模式的支持,但是我们先自己实现一遍。

/**
 * @author MiaoCheng
 * @date: Create in 2018/7/13
 * @description: 主题类接口
 * @modifide By:
 */
public interface Subject {
    /**
     * 注册观察者方法
     * @param observer 需要传入观察者对象
     */
    void registerObserver(Observer observer);

    /**
     * 删除观察者方法
     * @param observer 需要传入观察者对象
     */
    void removeObserver(Observer observer);

    /**
     * 当状态改变时,此方法被调用
     */
    void notifyObserver();
}
/**
 * @author MiaoCheng
 * @date: Create in 2018/7/13
 * @description: 观察者接口
 * @modifide By:
 */
public interface Observer {
    /**
     * 当状态改变时此方法会被调用
     * @param temp 温度
     * @param humidity 湿度
     * @param pressure 气压
     */
    void update(float temp, float humidity, float pressure);
}

/**
 * @author MiaoCheng
 * @date: Create in 2018/7/13
 * @description: 当应用需要展示时,调用此对象
 * @modifide By:
 */
public interface DisplayElement {
    /**
     * 展示信息
     */
    void display();
}

接下来,我们开始实现WeatherDate类,对比第一次实现的类,看看有什么不一样。

/**
 * @author MiaoCheng
 * @date: Create in 2018/7/16
 * @description: WeatherData类的实现
 * @modifide By:
 */
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(i);
        }
    }

    @Override
    public void notifyObserver() {
        // 循环通知每一个观察者更新数据,由于每个观察者都实现update方法,所以知道怎么更新
        for(int i = 0; i < observers.size(); i++){
            Observer observer = (Observer) observers.get(i);
            observer.update(temperature, humidity, pressure);
        }
    }

    public void measurementsChange(){
        //当更新数据时,通知观察者
        notifyObserver();
    }

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

}

/**
 * @author MiaoCheng
 * @date: Create in 2018/7/16
 * @description: 显示当前天气的应用
 * @modifide By:
 */
public class CurrentConditionDisplay implements Observer, DisplayElement{
    /**
     * 温度
     */
    private float temperature;
    /**
     * 湿度
     */
    private float humidity;
    /**
     * 压力
     */
    private float pressure;

    private Subject weatherData;

    /**
     * 通过构造器注册观察者对象
     * @param weatherData
     */
    public CurrentConditionDisplay(Subject weatherData){
        this.weatherData = weatherData;
        weatherData.registerObserver(this);
    }
    @Override
    public void update(float temp, float humidity, float pressure) {
        this.temperature = temp;
        this.humidity = humidity;
        this.pressure = pressure;
        display();
    }
    @Override
    public void display() {
        System.out.println("当前温度:"+ temperature+"℃ 当前湿度:" + humidity +" % 当前气压:" + pressure + "Pa");
    }
}

接下来就是测试之前实现的功能了,目前只实现了显示当前天气的应用:

/**
 * @author MiaoCheng
 * @date: Create in 2018/7/16
 * @description: 测试显示当前天气的应用
 * @modifide By:
 */
public class WeatherStationTest {
    @Test
    public void weatherTest(){
        WeatherData weatherData = new WeatherData();
        CurrentConditionDisplay currentConditionDisplay = new CurrentConditionDisplay(weatherData);
        weatherData.setMeasurements(22f,40f,20f);
    }
}

结果:


        如果需要添加新的应用,只需要实现对应的接口,并且注册,就可以接收到观察者发布的消息。如果不想接受,只需要调用对应的remove接口就可以。现在已经简单实现了一个观察者模式类,但是Java API内置了对观察者模式的支持。接下来再通过Java内置支持实现一下。以下是类图:


        在java.util包中包含了一个Observer接口和一个Observable类,上边的实现方式是使用推送的方式更新信息,如果使用Java内置支持,就可以使用主动拉取信息的方式来更新数据。Observer类对应的是观察者,Observable对应的是主题。

import java.util.Observable;

/**
 * @author MiaoCheng
 * @date: Create in 2018/7/16
 * @description: 通过继承 Observable 对象实现主题方法
 * @modifide By:
 */
public class WeatherData extends Observable {
    /**
     * 温度
     */
    private float temperature;
    /**
     * 湿度
     */
    private float humidity;
    /**
     * 压力
     */
    private float pressure;

    /**
     *  因为在父类中实现了对观察者的注册、删除等方法,所以不再需要记录观察者列表
     */
    public WeatherData(){

    }

    public void  measurementsChange(){
        // 在通知改变之前,要先调用setChange方法表示状态已经改变,此方法可以灵活调用,
        // 例如温度每改变一度才通知观察者,而不是只要改变就通知
        setChanged();
        notifyObservers();
    }

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

    public float getTemperature() {
        return temperature;
    }

    public float getHumidity() {
        return humidity;
    }

    public float getPressure() {
        return pressure;
    }
}
/**
 * @author MiaoCheng
 * @date: Create in 2018/7/16
 * @description: 显示当前天气的应用
 * @modifide By:
 */
import java.util.Observable;
import java.util.Observer;
public class CurrentConditionDisplay implements Observer, DisplayElement{
    /**
     * 观察者对象
     */
    Observable observable;
    /**
     * 温度
     */
    private float temperature;
    /**
     * 湿度
     */
    private float humidity;
    /**
     * 压力
     */
    private float pressure;

    /**
     * 通过构造器注册观察者对象
     * @param observable
     */
    public CurrentConditionDisplay(Observable observable){
        this.observable = observable;
        observable.addObserver(this);
    }

    /**
     * 更新数据,增加了一个参数作为附属信息
     * @param obs
     * @param arg
     */
    @Override
    public void update(Observable obs, Object arg) {
        if(obs instanceof WeatherData){
            WeatherData weatherData = (WeatherData) obs;
            this.temperature = weatherData.getTemperature();
            this.humidity = weatherData.getHumidity();
            this.pressure = weatherData.getPressure();
            display();
        }
    }
    @Override
    public void display() {
        System.out.println("当前温度:"+ temperature+"℃ 当前湿度:" + humidity +" % 当前气压:" + pressure + "Pa");
    }
}
这个结果和之前的运行结果是一样的,使得我们对于观察者模式的实现更加方便了。


        

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值