不可否认,观察者模式是JDK中使用最多的模式之一。通过了解观察者模式,至少我能够更好的梳理一对多的关系,还有对于松耦合的概念。使用观察者模式,至少我们能够切换对象在运行时是否能够被通知(更新消息等),简单来说,通过观察者模式,让我们对消息的更新更及时。
一 初步设计 观察者与气象站
引入一个很常见的案例。现在有一家气象站负责发布气象消息(WeatherData),作为不同的客户,会有不同的对于气象消息的需求,我们也就需要设计一个或者多个面向用户的消息面板获取气象总站的消息,并更新给用户满意的定制数据,用来获取报酬。
First,假设我们有3个信息模块与展示模块。
如上图所示,我们需要建立一个应用,利用WeatherData对象取得数据并更新3个布告板。
多的不说,开始敲第一部分代码。
public class WeatherData {
Board1 board1= new Board1();
Board2 board2= new Board2();
Board3 board3= new Board3();
public WeatherData() {
// TODO Auto-generated constructor stub
}
public void weatherChanged(){
//拟定获取3个数据源的方法
int temp=getTemp();
int humidity=getHumidity();
int pressure=getPressure();
//更新板子
board1.updata(temp, humidity, pressure);
board1.updata(temp, humidity, pressure);
board1.updata(temp, humidity, pressure);
}
}
updata()负责3个板子的更新工作。
public class Board1 {
public Board1() {
}
public void updata(int temp,int humidity,int pressure) {
}
}
写的时候感觉没有问题,但是仔细推敲一下,哦,呵呵。我们就盯着weatherChanged()方法看:
1.这种写法类似于针对具体实现编程,不是针对接口。以后增删板子必须修改程序。
2.三个板子类的更新方法入参一样,是不是可以封装成接口?
二 引入观察者模式
再次百度一波观察者模式概念:定义了对象的一对多关系,这样一来,当一个对象改变状态时,他的所有依赖者都会收到通知并自动更新。
我们明确目的,需要主题(被观察者,也就是WeatherData)一有变化就能通知到观察者(布告板)。同时为了业务需求,我们必须做到可以动态的删除或者增加观察者。
智者见智,实现观察者模式的方法有很多,我用到的无非是实现Subject和Observer接口。
磨刀不误砍柴工,我还是决定用visio重新画个图梳理一下思路。
然后,又一个蛋疼的概念就来了,松耦合。简单点说,就是观察者和主题不同过于了解彼此,差不多满足业务需求就可以了。
具体点,就是主题只知道观察者实现了Observer接口,其他的可以不管不问,只需要知道谁是观察者,并向其push信息就好了。主题部分的代码不需要修改就可以变更观察者。高端点说,主题可以在运行的时候随意的删增观察者,不需要考虑与观察者的兼容性,本身不会受到观察者变更的影响。这样一来,我们就顺水推舟的建立了具有弹性的OO系统,应对变化能力增强,原因在于对象间的依赖降到了最低。
设计原则:为了交互对象之间的松耦合设计而努力。
根据上面的流程梳理,我们再次流程图梳理一下我们的布告板工程。
根据以上流程图我们开始敲代码把。
1.三个需要实现的接口
public interface Subject {//主题接口
public void registerObserver(Observer o);//注册
public void removeObserver(Observer o);//删除
public void notifyObservers();//主题状态改变,通知所有观察者
}
public interface Observer {//所有观察者必须实现Observer接口
public void update(float temp, float humidity, float pressure);
}
public interface Display {//布告板需要显示时调用
public void display();
}
2.看一下我们上面的WeatherData类,我们对他进行优化。
public class WeatherData implements Subject{
private ArrayList<Observer> observers;
private float temp;
private float humidity;
private float pressure;
public WeatherData() {
observers = new ArrayList<Observer>();
}
@Override
public void register(Observer o) {
observers.add(o);
}
@Override
public void remove(Observer o) {
int i=observers.indexOf(o);
if (i>=0) {//确认需要删除的观察者在队列中
observers.remove(i);
}
}
@Override
public void notifys() {
//刷新队列中所有已经注册的观察者
for (int i = 0; i < observers.size(); i++) {
observers.get(i).update(temp, humidity, pressure);
}
}
public void weatherChanged() {
//气象站得知消息更新时调用,更新观察者(布告板)
notifys();
}
//主动刷新气象信息
public void setWeather(float temperature, float humidity, float pressure) {
this.temp = temperature;
this.humidity = humidity;
this.pressure = pressure;
weatherChanged();
}
//单独获取气象信息的3个get方法
public float getTemperature() {
return temp;
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
}
3.既然WeatherData已经完工,开始设计我们的布告板1。
public class Board1 implements Observer, Display{
private float temp;
private float humidity;
private Subject weatherData;
public Board1(Subject weatherData) {
this.weatherData = weatherData;
weatherData.register(this);
}
public void update(float temperature, float humidity, float pressure) {
this.temp = temperature;
this.humidity = humidity;
display();//当气象信息改变立即刷新布告板
}
public void display() {
System.out.println("Current conditions: " + temperature );
}
}
其他的布告板作为一个观察者,其实都是大同小异,都去继承Observcer和Display接口并一一实现就好。
我们这套拥有观察者特质的气象系统已经建造完毕了,我们可以用一个测试类测试一下:
public class WeatherTest {
public static void main(String[] args) {
WeatherData weatherData = new WeatherData();
CurrentConditionsDisplay currentDisplay = new Board1 (weatherData);
weatherData.setWeather(80, 65, 30.4f);
weatherData.setWeather(82, 70, 29.2f);
weatherData.setWeather(78, 90, 29.2f);
}
}
e嗯哼,结果符合预期。
三 JAVA内置的观察者模式。
如果你真的跟着这篇博客从头看到这里。。。我其实会有点无语的。。。因为,因为,其实JAVA里面有内置的观察者模式,= =看来你真的是对观察者模式一无所知。java.util内包含Observerble类和Observer接口,甚至比我们前面的subject更好用哟~
好吧,我们再次对系统进行修改,允许我再次插个图。
差异性。
1.我们发现最大差异就是主题WeatherData只需要继承系统自带的Observable类。观察者通过Observer接口实现不变,改变的是通过addObserver()和deleteObserver方法进行注册和解绑。
2.主题发送通知,需要先调用setChange()方法,先声明状态的改变。然后调用notify方法进行刷新观察者状态。基本上与我们上面大同小异。
public class WeatherData extends Observable{
private float temperature;
private float humidity;
private float pressure;
public WeatherData() {
// TODO Auto-generated constructor stub
}
public void measurementsChanged() {
setChanged();
//观察者需要改变什么数据,是需要观察者自己到主题那里去pull.
notifyObservers();
/*由主题主动的PUSH需要改变的数据对象给观察者
Object arg 其实就是 Observer接口中的update(Observable o, Object arg)方法中的第二个参数 */
//notifyObservers(temperature);
}
public void setMeasurements(float temperature, float humidity, float pressure) {
this.temperature = temperature;
this.humidity = humidity;
this.pressure = pressure;
measurementsChanged();
}
public float getTemperature() {
return temperature;
}
public float getHumidity() {
return humidity;
}
public float getPressure() {
return pressure;
}
}
这里我们需要需要注意Observable中的2个方法。百度上资料很多,我简单说明一下。
1. setChanged()
先看下源码中的注解
/**
* Marks this <tt>Observable</tt> object as having been changed; the
* <tt>hasChanged</tt> method will now return <tt>true</tt>.
*/
protected synchronized void setChanged() {
changed = true;
}
意思就是讲继承Observable的主题类(可观察者)状态改变为已更改。在notifyObservers()之前,一定要先调用这个方法来指示已经改变的主题状态。
同时,还有cleraChanged()方法讲change状态设置为false,hasChanged()返回当前change的状态。
2. notifyObserver()¬ifyObserver(Object arg)
带参数的notifyObservers(Object arg):这个参数Object arg 其实就是 Observer接口中的update(Observable o, Object arg)方法中的第二个参数 。就是通知观察者所改变的数据对象。简单理解就是由主题主动的PUSH需要改变的数据对象给观察者。
不带参数的方法,传递一个null数据对象给观察者,需要观察者主动到主题pull数据。
其他的不在复述。
下面看一下我们的布告板(观察者)有上面改变,直接上代码。
package ObserverTest;
import java.util.Observable;
import java.util.Observer;
public class CurrentConditionsDisplay implements Observer, DisplayElement {
Observable observable;
private float temperature;
private float humidity;
public CurrentConditionsDisplay(Observable observable) {
//构造方法需要引入一个主题类(继承Observable)
this.observable = observable;
//加入主题注册队列
observable.addObserver(this);
}
public void update(Observable obs, Object arg) {
if (obs instanceof WeatherData) {//判断主题是否对应 、
//默认为pull方式获取更新数据,这样适应性更强
WeatherData weatherData = (WeatherData)obs;
this.temperature = weatherData.getTemperature();
this.humidity = weatherData.getHumidity();
//更新面板数据
display();
}
}
public void display() {
System.out.println("Current conditions: " + temperature
+ "F degrees and " + humidity + "% humidity");
}
}
OK,搞到这一步,基本上观察者模式已经梳理完了,通过这篇博客的整理,我也对观察者模式的认知提高了一个层次。