设计模式学习--观察者模式(Observer Pattern)

设计模式学习--观察者模式(Oberser Pattern)

2013年5月18日 天气:热!
下午15:28 设计模式学习中
学习者:小巫



什么是观察者模式?

定义了对象之间的一对多依赖,这样一来,当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。


怎么设计一个观察者模式的程序?

确定两个对象:1. 主题
2.观察者

确定这两个对象之间的关系:

主题对象管理某些数据,一旦数据发生改变,会主动向观察者进行通知,然而观察者不必向主题进行索取。
主题并不知道具体的观察者是谁,这是它们之间的关系。

以上涉及到的设计原则:

为了交互对象之间的松耦合设计而努力


具体实例:气象站的实现

1. 定义一个主题接口Subject
package observerPattern;
/**
 * 主题接口
 * @author wwj
 *
 */
public interface Subject {
	public void registerObserver(Observer o);	//这两个方法都需要一个观察者作为变量,该观察者是用那个来注册和删除的
	public void removeObserver(Observer o);
	public void notifyObserver();				//当主题状态发生改变时,这个方法会被调用,以通知所有的观察者
}

2. 定义一个观察者接口Observer
package observerPattern;
/**
 * 观察者接口
 * @author wwj
 *
 */
public interface Observer {
	public void update(float temp, float humidity, float pressure);
}

3. 定义一般气象布告板接口DisplayElement
package observerPattern;

/**
 * 公告板接口
 * @author wwj
 *
 */
public interface DisplayElement {
	public void display();
}	


4. 定义主题类:WeatherData实现接口
package observerPattern;

import java.util.ArrayList;

/**
 * WeatherData实现了Subject接口
 * @author wwj
 *
 */
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 o) {
		observers.add(o);
	}

	@Override
	public void removeObserver(Observer o) {
		int i = observers.indexOf(o);
		if(i >= 0) {
			observers.remove(i);
		}
	}

	@Override
	public void notifyObserver() {
		for(int i = 0; i < observers.size(); i++) {
			Observer observer = (Observer)observers.get(i);
			observer.update(temperature, humidity, pressure);
		}
	}
	
	public void measurementsChanged() {
		notifyObserver();
	}
	
	public void setMeasurements(float temperature, float humidity, float pressure) {
		this.temperature = temperature;
		this.humidity = humidity;
		this.pressure = pressure;
		measurementsChanged();
	}

}

5. 定义四个布告板类实现观察者接口和布告板接口
package observerPattern;
/**
 * 观察者类实现观察者接口和显示板接口
 * @author wwj
 *
 */
public class CurrentConditionDisplay implements Observer, DisplayElement {
	private float temperature;
	private float humidity;
	private Subject weathderData;
	
	
	public CurrentConditionDisplay(Subject weathderData) {
		this.weathderData = weathderData;
		weathderData.registerObserver(this);		//注册
	}

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

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

}

package observerPattern;
/**
 * 天气统计布告板
 * @author wwj
 *
 */
public class StatisticsDisplay implements Observer, DisplayElement {
	private float maxTemp = 0.0f;;	//最大温度
	private float minTemp = 200;	//最小温度
	private float tempSum = 0.0f;	//统计温度和
	private int numReadings;		//统计温度次数
	private WeatherData weatherData;
	
	
	public StatisticsDisplay(WeatherData weatherData) {
		this.weatherData = weatherData;
		weatherData.registerObserver(this);
	}

	@Override
	public void display() {
		System.out.println("Avg/Max/Min temperature = " + (tempSum / numReadings) + "/" + maxTemp + "/" + minTemp);
	}

	@Override
	public void update(float temp, float humidity, float pressure) {
		tempSum += temp;
		numReadings++;
		
		if(temp > maxTemp) {
			maxTemp = temp;
		}
		if(temp < minTemp) {
			minTemp = temp;
		}
		
		display();
	}

}

package observerPattern;
/**
 * 天气预报布告板
 * @author wwj
 *
 */
public class ForecastDisplay implements Observer, DisplayElement {
	private float currentPressure = 29.92f;	//当前气压
	private float lastPressure;				//以往气压
	private WeatherData weatherData;
	
	public ForecastDisplay(WeatherData weatherData) {
		this.weatherData = weatherData;
		weatherData.registerObserver(this);
	}
	
	@Override
	public void display() {
		System.out.println("Forcast:");
		if(currentPressure > lastPressure) {
			System.out.println("Improving weather on the way!");
		} else if(currentPressure == lastPressure) {
			System.out.println("more of the same");
		} else if(currentPressure < lastPressure) {
			System.out.println("Watch out for cooler, rainy weather");
		}
	}

	@Override
	public void update(float temp, float humidity, float pressure) {
		lastPressure = currentPressure;
		currentPressure = pressure;
		display();
	}

}

package observerPattern;

/**
 * 酷热指数布告板
 * 
 * @author wwj
 * 注:那个计算酷热指数的公式不必深究
 */
public class HeatIndexDisplay implements Observer, DisplayElement {
	float heatIndex = 0.0f;
	private WeatherData weatherData;

	public HeatIndexDisplay(WeatherData weatherData) {
		this.weatherData = weatherData;
		weatherData.registerObserver(this);
	}

	public void update(float t, float rh, float pressure) {
		heatIndex = computeHeatIndex(t, rh);
		display();
	}
	
	private float computeHeatIndex(float t, float rh) {
		float index = (float)((16.923 + (0.185212 * t) + (5.37941 * rh) - (0.100254 * t * rh) 
			+ (0.00941695 * (t * t)) + (0.00728898 * (rh * rh)) 
			+ (0.000345372 * (t * t * rh)) - (0.000814971 * (t * rh * rh)) +
			(0.0000102102 * (t * t * rh * rh)) - (0.000038646 * (t * t * t)) + (0.0000291583 * 
			(rh * rh * rh)) + (0.00000142721 * (t * t * t * rh)) + 
			(0.000000197483 * (t * rh * rh * rh)) - (0.0000000218429 * (t * t * t * rh * rh)) +
			0.000000000843296 * (t * t * rh * rh * rh)) -
			(0.0000000000481975 * (t * t * t * rh * rh * rh)));
		return index;
	}

	public void display() {
		System.out.println("Heat index is " + heatIndex);
	}
}

6. 来吧,开始测试
package observerPattern;

/**
 * 测试类
 * @author wwj
 *
 */
public class WeatherStation {
	public static void main(String[] args) {
		//建立一个WeatherData对象
		WeatherData weatherData = new WeatherData();
		
		//第一个布告板
		CurrentConditionDisplay currentDisplay = new CurrentConditionDisplay(
				weatherData);
		StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
		ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
		HeatIndexDisplay heatIndexDisplay = new HeatIndexDisplay(weatherData);

		// 模拟新的气象数据
		weatherData.setMeasurements(80, 65, 30.4f);
		weatherData.setMeasurements(82, 70, 29.2f);
		weatherData.setMeasurements(78, 90, 29.2f);
	}
}

7. 测试结果:
Current coditions: 80.0F degress and 65.0% humidity
Avg/Max/Min temperature = 80.0/80.0/80.0
Forcast:
Improving weather on the way!
Heat index is 82.95535
Current coditions: 82.0F degress and 70.0% humidity
Avg/Max/Min temperature = 81.0/82.0/80.0
Forcast:
Watch out for cooler, rainy weather
Heat index is 86.90124
Current coditions: 78.0F degress and 90.0% humidity
Avg/Max/Min temperature = 80.0/82.0/78.0
Forcast:
more of the same
Heat index is 83.64967



以上的观察者模式实现是通过主题以“推”的方式通知观察者们,观察者可以在一次通知中一口气得到所有东西。

因为观察者与主题发生了争吵,观察者有自己的想法,希望能“拉”走主题的状态,然而Java内置的Observer模式就支持这样,下面来看看吧。

1. 继承Observable类的WeatherData(不再需要自定义接口了,但这样真的好吗?)
package weatherObservable;

import java.util.Observable;
/**
 * 使用Java内置的观察者模式
 * @author wwj
 *
 */
public class WeatherData extends Observable {
	private float temperature;
	private float humidity;
	private float pressure;
	
	/**
	 * 我们的构造器不再需要为了记住观察者们而建立数据结构了
	 */
	public WeatherData(){}
	
	public void measurementsChanged() {
		setChanged();		//Observable类方法
		notifyObservers();
	}
	
	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;
	}
	
}

2. 4个布告板中的代码稍微发生了点变化
package weatherObservable;

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


/**
 * 实现Java内置的观察者接口,布告板不变
 * @author wwj
 *
 */
public class CurrentConditionDisplay implements Observer, DisplayElement{
	Observable observable;
	private float temperature;
	private float humidity;
	
	public CurrentConditionDisplay(Observable observable) {
		this.observable = observable;
		observable.addObserver(this);		//登记为观察者
	}
	
	@Override
	public void display() {
		System.out.println("Current conditions: " + temperature + "F degrees and " + humidity + "% humidity");
	}

	
	/**
	 * 在这个方法当中,先确定可观察者属于WeatherData类型,然后利用getter方法获取温度和温度测量值,最后调用display();
	 */
	@Override
	public void update(Observable obs, Object arg) {
		if(obs instanceof WeatherData) {
			WeatherData weatherData = (WeatherData) obs;
			this.temperature = weatherData.getTemperature();
			this.humidity = weatherData.getHumidity();
			display();
		}
	}

}

package weatherObservable;

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

/**
 * 天气预报布告板
 * @author wwj
 *
 */
public class ForecastDisplay implements Observer, DisplayElement {
	private Observable observable;
	private float currentPressure = 29.92f;	//当前气压
	private float lastPressure;				//以往气压
	
	public ForecastDisplay(Observable observable) {
		this.observable = observable;
		observable.addObserver(this);
	}
	
	@Override
	public void display() {
		System.out.println("Forcast:");
		if(currentPressure > lastPressure) {
			System.out.println("Improving weather on the way!");
		} else if(currentPressure == lastPressure) {
			System.out.println("more of the same");
		} else if(currentPressure < lastPressure) {
			System.out.println("Watch out for cooler, rainy weather");
		}
	}


	@Override
	public void update(Observable o, Object arg) {
		if (o instanceof WeatherData) {
			WeatherData weatherData = (WeatherData)observable;
			lastPressure = currentPressure;
			currentPressure = weatherData.getPressure();
			display();
		}
	}

}

package weatherObservable;

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

/**
 * 天气统计布告板
 * @author wwj
 *
 */
public class StatisticsDisplay implements Observer, DisplayElement {
	private float maxTemp = 0.0f;;	//最大温度
	private float minTemp = 200;	//最小温度
	private float tempSum = 0.0f;	//统计温度和
	private int numReadings;		//统计温度次数
	private Observable observable;
	
	public StatisticsDisplay(Observable observable) {
		this.observable = observable;
		observable.addObserver(this);
	}

	@Override
	public void display() {
		System.out.println("Avg/Max/Min temperature = " + (tempSum / numReadings) + "/" + maxTemp + "/" + minTemp);
	}

	@Override
	public void update(Observable obs, Object arg) {
		if(obs instanceof WeatherData) {
			WeatherData weatherData = (WeatherData)obs;
			float temp = weatherData.getTemperature();
			tempSum += temp;
			numReadings++;

			if (temp > maxTemp) {
				maxTemp = temp;
			}
 
			if (temp < minTemp) {
				minTemp = temp;
			}

			display();
		}
	}


}

package weatherObservable;

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

/**
 * 酷热指数布告板
 * 
 * @author wwj
 * 注:那个计算酷热指数的公式不必深究
 */
public class HeatIndexDisplay implements Observer, DisplayElement {
	float heatIndex = 0.0f;
	private WeatherData weatherData;
	private Observable observable;

	public HeatIndexDisplay(Observable observable) {
		this.observable = observable;
		observable.addObserver(this);
	}

	private float computeHeatIndex(float t, float rh) {
		float index = (float)((16.923 + (0.185212 * t) + (5.37941 * rh) - (0.100254 * t * rh) 
			+ (0.00941695 * (t * t)) + (0.00728898 * (rh * rh)) 
			+ (0.000345372 * (t * t * rh)) - (0.000814971 * (t * rh * rh)) +
			(0.0000102102 * (t * t * rh * rh)) - (0.000038646 * (t * t * t)) + (0.0000291583 * 
			(rh * rh * rh)) + (0.00000142721 * (t * t * t * rh)) + 
			(0.000000197483 * (t * rh * rh * rh)) - (0.0000000218429 * (t * t * t * rh * rh)) +
			0.000000000843296 * (t * t * rh * rh * rh)) -
			(0.0000000000481975 * (t * t * t * rh * rh * rh)));
		return index;
	}

	public void display() {
		System.out.println("Heat index is " + heatIndex);
	}

	@Override
	public void update(Observable obs, Object arg) {
		if(obs instanceof WeatherData) {
			WeatherData weatherData = (WeatherData)observable;
			float t = weatherData.getTemperature();
			float rh = weatherData.getHumidity();
			heatIndex = computeHeatIndex(t, rh);
		}
		display();
	}
}

3. 测试类不变
package weatherObservable;

/**
 * 测试类
 * @author wwj
 *
 */
public class WeatherStation {
	public static void main(String[] args) {
		//建立一个WeatherData对象
		WeatherData weatherData = new WeatherData();
		
		//第一个布告板
		CurrentConditionDisplay currentDisplay = new CurrentConditionDisplay(
				weatherData);
		StatisticsDisplay statisticsDisplay = new StatisticsDisplay(weatherData);
		ForecastDisplay forecastDisplay = new ForecastDisplay(weatherData);
		HeatIndexDisplay heatIndexDisplay = new HeatIndexDisplay(weatherData);

		// 模拟新的气象数据
		weatherData.setMeasurements(80, 65, 30.4f);
		weatherData.setMeasurements(82, 70, 29.2f);
		weatherData.setMeasurements(78, 90, 29.2f);
	}
}

4. 但测试结果发生了变化:观察者被通知的次序发生了变化
Heat index is 82.95535
Forcast:
Improving weather on the way!
Avg/Max/Min temperature = 80.0/80.0/80.0
Current conditions: 80.0F degrees and 65.0% humidity
Heat index is 86.90124
Forcast:
Watch out for cooler, rainy weather
Avg/Max/Min temperature = 81.0/82.0/80.0
Current conditions: 82.0F degrees and 70.0% humidity
Heat index is 83.64967
Forcast:
more of the same
Avg/Max/Min temperature = 80.0/82.0/78.0
Current conditions: 78.0F degrees and 90.0% humidity

以上的实现被认为不是那么“正确”的,为什么呢?
有以下原因:
1. Observable是一个“类”,而不是一个接口,也没有实现一个接口,限制了它的使用和复用。
2. Observable将关键的方法保护起来了,违反了 “多用组合,少用继承”的设计原则。



当然在JDK中不只有这个地方用到了观察者模式,比如以下几个地方也用到了:
1. Swing
2. JavaBeans
3. RMI



好啦,第二个设计模式:观察者模式被小巫收入囊中,这实在太有趣了。
下一个模式将会是:装饰者模式。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值