23种设计模式之三(观察者模式)
观察者模式:(对象间的一种一对多依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新)
观察者模式又称为发布/订阅(Publish/Subscribe)模式,因此我们可以用报纸期刊的订阅来形象的说明:
报社方负责出版报纸;
用户订阅了该报社的报纸,那么只要报社发布了新报纸,就会通知用户,或发到用户手上.
如果用户不想再读报纸,可以取消订阅,这样,报社发布了新报纸就不会再通知用户.
理解以上的概念,就可以理解观察者模式,观察者模式中有主题(Subject)和观察者(Observer),分别对应报社和订阅用户(你).
观察者模式定义了对象之间的一对多的依赖关系,这样,当"一"的一方状态发生变化时,它所依赖的"多"的一方都会收到通知并且自动更新.如图:
它将观察者和被观察者的对象分离开。提高了应用程序的可维护性和重用性。
实现观察者模式有很多形式,一种是“注册---通知---撤销注册”的形式。
观察者Observer:所有潜在的观察者必须实现观察者接口,这个接口只有update方法,当主题改变时,它被调用。
具体观察者CurrentConditions/ForcastConditions: 具体观察者可以是任何实现了Observer接口的类。观察者必须注册具体主题,一边接收更新。
可观察者Subject: 主题接口,即可观察者Observable,对象使用此接口注册为观察者,或者把自己从观察者中删除,每个主题可以有多个观察者。
具体可观察者WeatherDataSt: 一个具体主题实现了主题接口,除了注册和撤销之外,具体主题还实现了notifyObservers()方法,这个方法用来在主题状态改变时更新所有观察者。具体主题也可能有设置和获取状态的方法。
简单示例:
Internet气象站项目:
提供温度、气压和湿度的接口
测量数据更新时需时时通知给第三方
需要设计开放型API,便于其他第三方公司也能接入气象站获取数据
观察者模式:
1 package com.java.hexter.internetweather.observer; 2 3 public interface Subject { 4 public void registerObserver(Observer o); 5 public void removeObserver(Observer o); 6 public void notifyObservers(); 7 }
1 package com.java.hexter.internetweather.observer; 2 3 public interface Observer { 4 public void update(float mTemperatrue,float mPressure,float mHumidity); 5 }
1 package com.java.hexter.internetweather.mode; 2 3 import java.util.ArrayList; 4 5 import com.java.hexter.internetweather.observer.Observer; 6 import com.java.hexter.internetweather.observer.Subject; 7 8 public class WeatherDataSt implements Subject{ 9 10 private float mTemperatrue; 11 private float mPressure; 12 private float mHumidity; 13 private ArrayList<Observer> mObservers;//泛型的动态数组,成员为Observer 14 public WeatherDataSt() 15 { 16 mObservers=new ArrayList<Observer>(); 17 } 18 19 public float getTemperature() 20 { 21 return mTemperatrue; 22 23 } 24 25 public float getPressure() 26 { 27 return mPressure; 28 29 } 30 31 public float getHumidity() 32 { 33 return mHumidity; 34 35 } 36 public void dataChange() 37 { 38 notifyObservers(); 39 } 40 41 42 43 44 45 public void setData(float mTemperatrue,float mPressure,float mHumidity) 46 { 47 this.mTemperatrue=mTemperatrue; 48 this.mPressure=mPressure; 49 this.mHumidity=mHumidity; 50 dataChange(); 51 } 52 53 //实现接口的方法 54 @Override 55 public void registerObserver(Observer o) { 56 // TODO Auto-generated method stub 57 mObservers.add(o); 58 } 59 60 @Override 61 public void removeObserver(Observer o) { 62 // TODO Auto-generated method stub 63 if(mObservers.contains(o)) 64 {mObservers.remove(o);} 65 } 66 67 @Override 68 public void notifyObservers() { 69 // 遍历通知每个Observer 70 for(int i=0,len=mObservers.size();i<len;i++) 71 { 72 mObservers.get(i).update(getTemperature(), getPressure(), getHumidity()); 73 } 74 } 75 76 }
1 package com.java.hexter.internetweather.mode; 2 3 import com.java.hexter.internetweather.observer.Observer; 4 5 public class CurrentConditions implements Observer { 6 7 private float mTemperatrue; 8 private float mPressure; 9 private float mHumidity; 10 11 @Override 12 public void update(float mTemperatrue, float mPressure, float mHumidity) { 13 // TODO Auto-generated method stub 14 this.mHumidity = mHumidity; 15 this.mPressure = mPressure; 16 this.mTemperatrue = mTemperatrue; 17 display(); 18 } 19 20 public void display() { 21 System.out.println("***Today mTemperatrue:" + mTemperatrue + "***"); 22 System.out.println("***Today mPressure:" + mPressure + "***"); 23 System.out.println("***Today mHumidity:" + mHumidity + "***"); 24 25 } 26 27 }
1 package com.java.hexter.internetweather.mode; 2 3 import com.java.hexter.internetweather.observer.Observer; 4 5 6 public class ForcastConditions implements Observer{ 7 private float mTemperatrue; 8 private float mPressure; 9 private float mHumidity; 10 @Override 11 public void update(float mTemperatrue, float mPressure, float mHumidity) { 12 // TODO Auto-generated method stub 13 this.mTemperatrue=mTemperatrue; 14 this.mPressure=mPressure; 15 this.mHumidity=mHumidity; 16 17 display(); 18 } 19 public void display() 20 { 21 System.out.println("**明天温度:"+(mTemperatrue+Math.random())+"**"); 22 System.out.println("**明天气压:"+(mPressure+10*Math.random())+"**"); 23 System.out.println("**明天湿度:"+(mHumidity+Math.random())+"**"); 24 } 25 }
1 package com.java.hexter.internetweather.mode; 2 3 4 5 public class InternetWeather { 6 7 public static void main(String[] args) { 8 9 CurrentConditions mCurrentConditions; 10 ForcastConditions mForcastConditions; 11 WeatherDataSt mWeatherDataSt; 12 13 mWeatherDataSt=new WeatherDataSt(); 14 mCurrentConditions=new CurrentConditions(); 15 mForcastConditions=new ForcastConditions(); 16 17 mWeatherDataSt.registerObserver(mCurrentConditions); 18 mWeatherDataSt.registerObserver(mForcastConditions); 19 20 mWeatherDataSt.setData(30, 150, 40); 21 mWeatherDataSt.removeObserver(mCurrentConditions); 22 mWeatherDataSt.setData(40, 250, 50); 23 } 24 25 }
***Today mTemperatrue:30.0*** ***Today mPressure:150.0*** ***Today mHumidity:40.0*** **明天温度:30.648753273490808** **明天气压:153.97277811893593** **明天湿度:40.1087761806788** **明天温度:40.27392255361601** **明天气压:258.94655737302077** **明天湿度:50.61566135267864**
观察者模式之内置观察者:
在java.util包中包含有基本的Observer接口和Observable抽象类.功能上和Subject接口和Observer接口类似.不过在使用上,就方便多了,因为许多功能比如说注册,删除,通知观察者的那些功能已经内置好了.
如何使对象变为观察者?
实现观察者接口(java.util.Observer),然后调用Observable对象的addObserver()方法.不想再当观察者时,调用deleteObserver()就可以了.
被观察者(主题)如何发出通知?
第一步:先调用setChanged()方法,标识状态已经改变的事实.
第二步:调用notifyObservers()方法或者notifyObservers(Object arg),这就牵扯到推(push)和拉(pull)的方式传送数据.如果想用push的方式"推"数据给观察者,可以把数据当做数据对象传送给notifyObservers(Object arg)方法,其中的arg可以为任意对象,意思是你可以将任意对象传送给每一个观察者.如果调用不带参数的notifyObserver()方法,则意味着你要使用pull的方式去主题对象中"拉"来所需要的数据.
观察者如何接收通知?;
观察者只需要实现一个update(Observable o,Object arg)方法,第一个参数o,是指定通知是由哪个主题下达的,第二个参数arg就是上面notifyObserver(Object arg)里传入的数据,如果不传该值,arg为null.
1 package com.java.hexter.internetweather.jv; 2 3 import java.util.Observable; 4 5 //继承定义好的Observable类(相当于前面自己定义的Object); 6 //所以Subject接口中的方法都已经被实现,可以直接使用 7 public class WeatherData extends Observable{ 8 private float mTemperatrue; 9 private float mPressure; 10 private float mHumidity; 11 12 public float getTemperature() { 13 return mTemperatrue; 14 } 15 16 public float getPressure() { 17 return mPressure; 18 } 19 20 public float getHumidity() { 21 return mHumidity; 22 } 23 24 25 public void dataChange() { 26 this.setChanged();//看数据是否有变化,当为true时进行通知;可以设计通知的灵活性 27 this.notifyObservers(new Data(getTemperature(),getPressure(),getHumidity())); 28 } 29 30 31 public void setData(float mTemperatrue,float mPressure,float mHumidity) { 32 this.mTemperatrue=mTemperatrue; 33 this.mPressure=mPressure; 34 this.mHumidity=mHumidity; 35 dataChange(); 36 } 37 38 //定义的该类是为了给notifyObservers方法传递数据 39 public class Data { 40 public float mTemperatrue; 41 public float mPressure; 42 public float mHumidity; 43 public Data(float mTemperatrue,float mPressure,float mHumidity) { 44 this.mTemperatrue=mTemperatrue; 45 this.mPressure=mPressure; 46 this.mHumidity=mHumidity; 47 } 48 } 49 50 }
1 package com.java.hexter.internetweather.jv; 2 3 import java.util.Observable; 4 import java.util.Observer; 5 6 import com.java.hexter.internetweather.jv.WeatherData.Data; 7 8 //实现定义好的Observer 9 public class CurrentConditions implements Observer { 10 11 private float mTemperatrue; 12 private float mPressure; 13 private float mHumidity; 14 15 @Override 16 public void update(Observable arg0, Object arg1) { 17 //arg0代表各个观察者 18 //arg1代表的是一组数据(如:Data对象),可以查看Observable中notifyObservers的实现即可理解 19 StackTraceElement stack[] = (new Throwable()).getStackTrace(); 20 System.out.println("当前通知的观察者(用户):"+ stack[0].getClassName()); 21 this.mTemperatrue=((Data)(arg1)).mTemperatrue; 22 this.mPressure=((Data)(arg1)).mPressure; 23 this.mHumidity=((Data)(arg1)).mHumidity; 24 display(); 25 } 26 27 public void display() 28 { 29 System.out.println("***Today mTemperatrue:" +mTemperatrue+"***"); 30 System.out.println("***Today mPressure:" +mPressure+"***"); 31 System.out.println("***Today mHumidity:" +mHumidity+"***"); 32 } 33 34 35 }
1 package com.java.hexter.internetweather.jv; 2 3 import java.util.Observable; 4 import java.util.Observer; 5 6 import com.java.hexter.internetweather.jv.WeatherData.Data; 7 8 9 public class ForcastConditions implements Observer { 10 11 private float mTemperatrue; 12 private float mPressure; 13 private float mHumidity; 14 @Override 15 public void update(Observable arg0, Object arg1) { 16 //arg0代表各个观察者 17 //arg1代表的是一组数据(如:Data对象),可以查看Observable中notifyObservers的实现即可理解 18 StackTraceElement stack[] = (new Throwable()).getStackTrace(); 19 System.out.println("当前通知的观察者(用户):"+ stack[0].getClassName()); 20 this.mTemperatrue=((Data)(arg1)).mTemperatrue; 21 this.mPressure=((Data)(arg1)).mPressure; 22 this.mHumidity=((Data)(arg1)).mHumidity; 23 display(); 24 } 25 26 public void display() 27 { 28 System.out.println("***Tomorrow mTemperatrue:" +(mTemperatrue+1)+"***"); 29 System.out.println("***Tomorrow mPressure:" +(mPressure+1)+"***"); 30 System.out.println("***Tomorrow mHumidity:" +(mHumidity+1)+"***"); 31 } 32 33 34 }
1 package com.java.hexter.internetweather.mode; 2 3 4 5 public class InternetWeather { 6 7 public static void main(String[] args) { 8 9 CurrentConditions mCurrentConditions; 10 ForcastConditions mForcastConditions; 11 WeatherDataSt mWeatherDataSt; 12 13 mWeatherDataSt=new WeatherDataSt(); 14 mCurrentConditions=new CurrentConditions(); 15 mForcastConditions=new ForcastConditions(); 16 17 mWeatherDataSt.registerObserver(mCurrentConditions); 18 mWeatherDataSt.registerObserver(mForcastConditions); 19 20 mWeatherDataSt.setData(30, 150, 40); 21 mWeatherDataSt.removeObserver(mCurrentConditions); 22 mWeatherDataSt.setData(40, 250, 50); 23 } 24 25 }
当前通知的观察者(用户):com.java.hexter.internetweather.jv.ForcastConditions ***Tomorrow mTemperatrue:31.0*** ***Tomorrow mPressure:151.0*** ***Tomorrow mHumidity:41.0*** 当前通知的观察者(用户):com.java.hexter.internetweather.jv.CurrentConditions ***Today mTemperatrue:30.0*** ***Today mPressure:150.0*** ***Today mHumidity:40.0*** 当前通知的观察者(用户):com.java.hexter.internetweather.jv.ForcastConditions ***Tomorrow mTemperatrue:36.0*** ***Tomorrow mPressure:151.0*** ***Tomorrow mHumidity:61.0***
结论:
内置观察者模式继承的Observable接口,内部实现了注册,删除,通知功能;
当还需要同时使用另外的Subject实现类时,就会变得很麻烦,导致不便于修改。
优点:
1、 Subject和Observer之间是松偶合的,不存在依赖;简单来说就是异步的,交互过程中并不需要等待,不需要知道方法是如何实现的;分别可以各自独立改变。
2、 对象内部是高内聚的(紧耦合);
2、 Subject在发送广播通知的时候,无须指定具体的Observer,Observer可以自己决定是否要订阅Subject的通知。
3、 遵守大部分GRASP原则和常用设计原则,高内聚、低偶合。
缺陷:
1、 松偶合导致代码关系不明显,有时可能难以理解。
2、 如果一个Subject被大量Observer订阅的话,在广播通知的时候可能会有效率问题。(毕竟只是简单的遍历)
应用场景:
1、 对一个对象状态的更新,需要其他对象同步更新,而且其他对象的数量动态可变。
2、 对象仅需要将自己的更新通知给其他对象而不需要知道其他对象的细节。