3、观察者模式

观察者模式

常用于:对象间多对一依赖。被依赖的对象为Subject,依赖的对象为Observer,Subject通知Observer变化。

生活中的例子对应:


1>订牛奶
奶站--Subject:是一个接口,实现功能:注册、移除、通知
用户--Observer:也是一个接口,实现功能:接收输入



2>气象播报

气象站--Subject

公告牌--Observer


以气象播报为例:

vs1:什么都不用,想当然直接写出来的情况:


提供数据接口的气象站 WeatherData.java :

public class WeatherData {

	private float mTemperature;
	private float mPressure;
	private float mHumidity;
	private CurrentConditions mCurrentConditions = null;
	
	public WeatherData(CurrentConditions currentConditions){
		mCurrentConditions = currentConditions;
	}
	
	public float getTemperature() {
		return mTemperature;
	}
	
	public float getPressure() {
		return mPressure;
	}
	
	public float getHumidity() {
		return mHumidity;
	}
	
	public void dataChanged(){
		mCurrentConditions.update(getTemperature(), getPressure(), getHumidity());
	}
	
	public void setData(float temperature, float pressure, float humidity){
		mTemperature = temperature;
		mPressure = pressure;
		mHumidity = humidity;
		dataChanged();
	}
}



接收气象数据的公告牌 CurrentConditions.java:

public class CurrentConditions {

	private float mTemperature;
	private float mPressure;
	private float mHumidity;
	
	public void update(float temperature, float pressure, float humidity){
		mTemperature = temperature;
		mPressure = pressure;
		mHumidity = humidity;
		display();
	}
	
	public void display(){
		System.out.println("Today's temperature:"+ mTemperature);
		System.out.println("Today's pressure:"+ mPressure);
		System.out.println("Today's humidity:"+ mHumidity);
	}
}



写个类(WeatherTest.java)测试一下:

public class WeatherTest {

	public static void main(String[] args) {
		CurrentConditions mCurrentConditions; 
		WeatherData mWeatherData;
		mCurrentConditions = new CurrentConditions();
		mWeatherData = new WeatherData(mCurrentConditions);
		
		mWeatherData.setData(30, 150, 40);
	}
}


运行结果:

Today's temperature:30.0
Today's pressure:150.0
Today's humidity:40.0

很显然可以运行,但是当增多一个接收信息的公告牌时,改动很麻烦(flag)。


vs2:采用装饰者模式


先定义一个接口 Subject.java:

public interface Subject {

	public void registerObserver(Observer o);
	public void removeObserver(Observer o);
	public void notifyObservers();
}
创建注册、移除、通知三个方法


再定义一个接口 Observer.java:

public interface Observer {

	public void update(float temperature, float pressure, float humidity);
}
创建更新数据的update()方法,接收传进来的数据。


提供数据接口的气象站 WeatherData.java 很明显要实现 Subject.java 接口:

public class WeatherData implements Subject {
	
	private float mTemperature;
	private float mPressure;
	private float mHumidity;
	private ArrayList<Observer> mObservers = null;
	
	public WeatherData(){
		mObservers = new ArrayList<Observer>();
	}
	
	public float getTemperature() {
		return mTemperature;
	}
	
	public float getPressure() {
		return mPressure;
	}
	
	public float getHumidity() {
		return mHumidity;
	}
	
	public void dataChanged(){
		notifyObservers();
	}
	
	public void setData(float temperature, float pressure, float humidity){
		mTemperature = temperature;
		mPressure = pressure;
		mHumidity = humidity;
		dataChanged();
	}

	@Override
	public void registerObserver(Observer o) {
		
		mObservers.add(o);
	}

	@Override
	public void removeObserver(Observer o) {

		mObservers.remove(o);
	}

	@Override
	public void notifyObservers() {

		for(int i = 0; i < mObservers.size(); i++){
			//这里有两种方式通知:
			//vs1:直接把参数传过去
			mObservers.get(i).update(getTemperature(), getPressure(), getHumidity());
			//vs2:只是通知观察者们有新消息了,需要什么参数观察者们自己去取(更适合扩展),取回来的数据再进行解析
		}
	}
}


而作为接收气象数据的公告牌有两个,两个都要实现Observer.java接口:

CurrentConditions.java:

public class CurrentConditions implements Observer {

	private float mTemperature;
	private float mPressure;
	private float mHumidity;
	
	@Override
	public void update(float temperature, float pressure, float humidity) {
		mTemperature = temperature;
		mPressure = pressure;
		mHumidity = humidity;
		display();
	}

	public void display(){
		System.out.println("Today's temperature:"+ mTemperature);
		System.out.println("Today's pressure:"+ mPressure);
		System.out.println("Today's humidity:"+ mHumidity);
	}
	
}


ForecastConditions.java:

public class ForecastConditions implements Observer {

	private float mTemperature;
	private float mPressure;
	private float mHumidity;
	
	@Override
	public void update(float temperature, float pressure, float humidity) {

		mTemperature = temperature;
		mPressure = pressure;
		mHumidity = humidity;
		display();
	}

	public void display(){
		System.out.println("明天温度:"+ (mTemperature+Math.random()));
		System.out.println("明天气压:"+ (mPressure+Math.random()));
		System.out.println("明天湿度:"+ (mHumidity+Math.random()));
	}
}


这样一来,就用装饰者模式重新实现了一下这个气象播报例子,写个类(WeatherTest2.java)测试一下:

public class WeatherTest2 {

	public static void main(String[] args) {
		
		CurrentConditions mCurrentConditions;
		ForecastConditions mForecastConditions;
		WeatherData mWeatherData;
		
		mWeatherData = new WeatherData();
		mCurrentConditions = new CurrentConditions();
		mForecastConditions = new ForecastConditions();
		
		mWeatherData.registerObserver(mCurrentConditions);
		mWeatherData.registerObserver(mForecastConditions);
		
		mWeatherData.setData(30, 150, 40);
	}
}

运行结果:

Today's temperature:30.0
Today's pressure:150.0
Today's humidity:40.0
明天温度:30.641740621608005
明天气压:150.4129630982695
明天湿度:40.283201107445485


改动一下WeatherTest2.java再测试一下:

public class WeatherTest2 {

	public static void main(String[] args) {
		
		CurrentConditions mCurrentConditions;
		ForecastConditions mForecastConditions;
		WeatherData mWeatherData;
		
		mWeatherData = new WeatherData();
		mCurrentConditions = new CurrentConditions();
		mForecastConditions = new ForecastConditions();
		
		mWeatherData.registerObserver(mCurrentConditions);
		mWeatherData.registerObserver(mForecastConditions);
		
		mWeatherData.removeObserver(mCurrentConditions);
		mWeatherData.setData(35, 160, 50);
	}
}

运行结果:

明天温度:35.69158101675214
明天气压:160.32248284833398
明天湿度:50.3754990869303

嗯,全部都运行成功了。


vs3:Java内置观察者

其实,还有第三种方式,更简便的方式,不过有一点局限性就是了。


按我自己的理解,如果可以用Java内置观察者的话,就直接用。
如果不可以的话(比如要继承其他的第三方接口,又要继承Observable时),就自己定义吧。


Java内部有一个 Java内置观察者,使用了观察者模式。

Subject对应的是Observable,也实现了注册、移除、通知三个功能。

Observer对应的还是Observer,也还是一个接口。(之所以还是接口,是因为观察者可能会选择直接通过参数传递过来数据,也可能仅仅想被通知一下,然后自己去取数据)

唯一不同的是Observable是一个类而不是接口,所以观察者需要extends(而不是implements)Observable(java.util.Observable)类


提供数据接口的气象站 WeatherData.java,继承了Observable类:

public class WeatherData extends Observable {
	
	private float mTemperature;
	private float mPressure;
	private float mHumidity;
	
	public float getTemperature() {
		return mTemperature;
	}
	
	public float getPressure() {
		return mPressure;
	}
	
	public float getHumidity() {
		return mHumidity;
	}
	
	public void dataChanged(){
		
		//使通知观察者时更具灵活性(比如想实现超过0.5度的变化才通知时,就可以在超过0.5度的逻辑中写下这句代码)
		this.setChanged();
		
		//vs1:直接推送信息给观察者
		this.notifyObservers(new Data(getTemperature(), getPressure(), getHumidity()));
		//vs2:仅仅通知观察者,需要什么让观察者自己去取
	}
	
	public void setData(float temperature, float pressure, float humidity){
		mTemperature = temperature;
		mPressure = pressure;
		mHumidity = humidity;
		dataChanged();
	}
	
	public class Data {
		
		public float mTemperature;
		public float mPressure;
		public float mHumidity;
		
		public Data(float temperature, float pressure, float humidity){
			
			mTemperature = temperature;
			mPressure = pressure;
			mHumidity = humidity;
		}
	}
}



很明显接收气象数据的另两个公告牌就要实现Observer接口了:

CurrentConditions.java:

public class CurrentConditions implements Observer {

	private float mTemperature;
	private float mPressure;
	private float mHumidity;

	@Override
	public void update(Observable o, Object arg) {

		//Object是notifyObservers()方法传递过来的参数,传过来时事Data类,所以现在也要转换成Data类
		this.mTemperature = ((Data)(arg)).mTemperature;
		this.mPressure = ((Data)(arg)).mPressure;
		this.mHumidity = ((Data)(arg)).mHumidity;
		display();
	}
	
	public void display(){
		System.out.println("Today's temperature:"+ mTemperature);
		System.out.println("Today's pressure:"+ mPressure);
		System.out.println("Today's humidity:"+ mHumidity);
	}
	
}


ForecastConditions.java:

public class ForecastConditions implements Observer {

	private float mTemperature;
	private float mPressure;
	private float mHumidity;

	@Override
	public void update(Observable o, Object arg) {

		//Object是notifyObservers()方法传递过来的参数,传过来时事Data类,所以现在也要转换成Data类
		this.mTemperature = ((Data)(arg)).mTemperature;
		this.mPressure = ((Data)(arg)).mPressure;
		this.mHumidity = ((Data)(arg)).mHumidity;
		display();
	}
	
	public void display(){
		System.out.println("Tomorrow's temperature:"+ (mTemperature+1));
		System.out.println("Tomorrow's pressure:"+ (mPressure+1));
		System.out.println("Tomorrow's humidity:"+ (mHumidity+1));
	}
	
}



没错,这样就写完了,写个类(WeatherTest3.java)测试一下吧:

public class WeatherTest3 {

	public static void main(String[] args) {
		
		CurrentConditions mCurrentConditions;
		ForecastConditions mForecastConditions;
		WeatherData mWeatherData;
		
		mWeatherData = new WeatherData();
		mCurrentConditions = new CurrentConditions();
		mForecastConditions = new ForecastConditions();
		
		mWeatherData.addObserver(mCurrentConditions);
		mWeatherData.addObserver(mForecastConditions);
		
		mWeatherData.setData(30, 150, 40);
		
		mWeatherData.deleteObserver(mCurrentConditions);
		mWeatherData.setData(35, 150, 60);
	}
}


运行结果:

Tomorrow's temperature:31.0
Tomorrow's pressure:151.0
Tomorrow's humidity:41.0
Today's temperature:30.0
Today's pressure:150.0
Today's humidity:40.0
Tomorrow's temperature:36.0
Tomorrow's pressure:151.0
Tomorrow's humidity:61.0

咦,这里发现一个问题,我先加了一个CurrentConditions类的Observer,再 加了一个ForecastConditions类的Observer,为什么先输出的是Tomorrow的数据(ForecastConditions类的)呢?


原来自己定义Subject、Observer的话,是先注册先通知,先进先出原则
Java内置观察者刚好相反,是先注册后通知,先进后出原则。


所以这里才会反过来显示。


气象站Subject有新数据过来了,调用顺序:dataChange()-->notifyObservers()-->update() [update observers中相关数据]
公告牌Observer


嗯,至此,例子就全部整理完了。


下面简单说下观察者模式的特点:

特点么,

低耦合:
对象之间。
只需要知道要调用的对方的那个函数即可,其他诸如怎么实现的,数据怎么传过来的都不用管。
打个比方:Subject挂了,Observer照样运行,只不过可能显示不了信息了而已。


高内聚:
对象内部。






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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值