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

概述

  如果此前你已经阅读过《Head First 设计模式》这本书,那么你现在可以跳过本文的阅读。当然,如果你想温故而知新,非常欢迎。因为本文添加了一个新的实例来说明观察者模式。


版权说明

商业转载请联系作者获得授权,非商业转载请注明出处。
本文作者:Q-WHai
发表日期: 2015年11月25日
本文链接:http://blog.csdn.net/lemon_tree12138/article/details/51437883
来源:CSDN
更多内容:分类 >> 设计模式


使用环境

  不知道大家在开发的过程有没有遇到过事物之间的通信问题。之前我在开发Android程序的时候,就有遇到过这样的情况,我的ListView中的Item中的一个按钮的点击事件,这个事件我要想让外层的Fragment来得知这一活动。在开发初期的确是让人困惑,因为我不想去写太多的广播(虽然广播是比较易用且使用方便的方法,但是太多的广播必然会让程序出现意想不到的状况发生)。

  本文会从两个实例(分别是“气象观测站”和“计时器应用”)的角度出发,来做一个详细的说明。

  在实例之前,我们先来看一下,观察者模式的类图(点击查看图片来源):


图-0 观察者模式类图

  从类图中我们可以看到,观察者的内容能够获得较快的更新的原因是因为被观察者在数据有修改的时候去及时通知所有的观察者了。

实例详解

1.气象观测站

(1)过程说明

  这里我们的需求是可以动态添加或是移除一个关于气象的显示布告板。而这个布告板也是可以动态更新显示数据的。

  比方说,目前有三个布告板:天气、森林、统计数据布告板。当我们气象站中的相关数据发生变化时,我们就去更新所有这些布告板的数据。你可能会说这个简单啊,只要在每个布告板上添加一个时间触发器就好了啊。每个布告板又是相互独立的,所以还可以添加上多线程操作。对于这个问题,这样写是可以的。可是,如果现在需求里需要新增一个新的布告板,展示的是不同的内容。于是,新的布告板又要新重编写所有逻辑,气象站里也要新增一些逻辑操作来支持这种新的需求。

  这样是不是有点麻烦?要是我们的气象站对象可以一直不变就好了,因为气象站可能会是一个服务器端的代码。气象站里返回的数据是一些性质不变的数据。这里就是温度、湿度、气压。

  主题每次更新都去更新这三个值,然后去通知所有的观察者说现在数据有变化,你们现在都刷新一下布告板吧。于是,观察者们就开始更新布告板的显示。其过程如下图-1所示:


图-1 气象观察站示意图

(2)代码

主题模块

  主题对象有3个基础操作:注册新的观察者、通知所有观察者更新数据、移除取消观察的观察者对象。

下面是接口实现(Subject.java):

public interface Subject {

	public void registerObserver(Observer o);
	
	public void removeObserver(Observer o);
	
	public void notifyObservers();
}
具体主题类实现(WeatherData.java):
public class WeatherData implements Subject {

	private ArrayList observers = null; // 观察者列表
	
	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(i);
		}
	}

	/**
	 * 更新通知所有的观察者
	 */
	@Override
	public void notifyObservers() {
		for (int i = 0; i < observers.size(); i++) {
			Observer 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();
	}
}

观察者模块

更新接口Observer:

public interface Observer {

	public void update(float temperature, float humidity, float pressure);
}
展示接口(DisplayElement):
public interface DisplayElement {

	public void display();
}

本例中的观察者类型比较多,这里只取其中之一进行介绍。例如:StatisticsDisplay.java

public class StatisticsDisplay implements Observer, DisplayElement {

	private float temperature; // 温度
	private float humidity; // 湿度
	private Subject weatherData = null;
	
	public StatisticsDisplay(Subject weatherData) {
		this.weatherData = weatherData;
		weatherData.registerObserver(this);
	}
	
	@Override
	public void display() {
		System.out.println("Statistics: " + temperature + "F degrees and " + humidity + "% humidity");
	}

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

2.动态计时器

(1)过程说明

  关于动态计时器,这里的功能就是可以轻松添加一个或是移除一个计时器。实例里模拟的是倒计时。

  我们可以让其存在一个永远不变的量,那就是真实的时间。这个真实的时间T_S可以是使用System.currentTimeMillis()获得时间戳,也可以是一个相对的时间(时间戳本身就是相对时间)。我们把这个“真实”的,永远存在着的时间看成是一个主题(被观察者),可以被不同的观察者进行订阅。而这里的观察者就是我们所说的计时器T_O

  我们可以让T_S在每隔一个时间单位就发布一个消息,即向其所有的订阅者(观察者)说明此时时间已经改变了。订阅者们就可以做出相应的更新操作。我们可以从图-2中看到这个过程。


图-2 动态计时器控制过程示意图

(2)代码

主题模块

  我们的主题对象需要有2个基本操作,注册新的观察者、更新通知。这里我增加了一个新的操作,那就是当我们的计时器倒数结束时,我们就把这个观察者计时器从主题的观察者列表中移除。

接口实现如下(TimerSubject.java):

public interface TimerSubject {

    /**
     * 为新的观察者实现注册服务
     *
     * @param o
     *          观察者
     */
    public void registerObserver(TimerObserver o);

    /**
     * 移除某一个观察者对象
     *
     * @param o
     *          观察者
     */
    public void removeObserver(TimerObserver o);

    /**
     * 更新通知所有的观察者主题状态已经改变
     */
    public void notifyObservers();
}
主题类(NagaTimer.java)代码如下:
public class NagaTimer implements TimerSubject {

    private long mCurrentStamp = 0L;

    private List<TimerObserver> mObservers = null;

    public NagaTimer() {
        if (mObservers == null) {
            mObservers = new ArrayList<>();
        }
    }

    @Override
    public void registerObserver(TimerObserver o) {
        if (mObservers != null) {
            mObservers.add(o);
        }
    }

    @Override
    public void removeObserver(TimerObserver o) {
        if (mObservers == null) {
            return;
        }

        mObservers.remove(o);
    }

    /**
     * 更新通知所有的观察者
     */
    @Override
    public void notifyObservers() {
        if (mObservers == null || mObservers.size() == 0) {
            return;
        }

        for (int i = 0; i < mObservers.size(); i++) {
            CountDownTimer countDownTimer = (CountDownTimer)mObservers.get(i);
            if (countDownTimer.isDone()) {
                removeObserver(mObservers.get(i));
            } else {
                countDownTimer.update(mCurrentStamp);
            }
        }
    }

    private void measurementsChanged() {
        notifyObservers();
    }

    public void setMeasurements(long currentStamp) {
        mCurrentStamp = currentStamp;
        measurementsChanged();
    }
}

观察者模块

  作为观察者,它可以去根据主题的改变进行一些合理的更新操作。本实例中是时间上的倒数。所以需要有一个更新操作和展示操作。

接口实现(TimerObserver.java):

public interface TimerObserver {

    /**
     * 主题对象只做一件事情,就是更新当前时间
     *
     * @param stamp
     */
    public void update(long stamp);
}
接口实现(TimerDisplayable.java):
public interface TimerDisplayable {

    public void display();
}
观察者(CountDownTimer.java):
public class CountDownTimer implements TimerObserver, TimerDisplayable {

    private String mName;
    private long mStartStamp;
    private long mCountdownStamp;
    private long mCurrentStamp = 0L;

    public CountDownTimer(String name, long countdown) {
        this.mStartStamp = System.currentTimeMillis();
        this.mName = name;
        this.mCountdownStamp = countdown;
    }

    @Override
    public void display() {
        if (mCurrentStamp - mStartStamp <= mCountdownStamp) {
            System.out.println(getName() + "还剩" + ((mCountdownStamp - (mCurrentStamp - mStartStamp)) / 1000) + "s");
        }
    }

    @Override
    public void update(long stamp) {
        mCurrentStamp = stamp;
        display();
    }

    public boolean isDone() {
        if (mCurrentStamp - mStartStamp >= mCountdownStamp) {
            return true;
        }

        return false;
    }

    public String getName() {
        return mName;
    }
}

运行图例

图-3 气象观测站运行示意图


图-4 计时器运行示意图


GitHub源码下载:

https://github.com/William-Hai/DesignPatternCollections

原文地址:https://www.cnblogs.com/fengju/p/6336030.html