观察者模式
常用于:对象间多对一依赖。被依赖的对象为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照样运行,只不过可能显示不了信息了而已。
高内聚:
对象内部。