Java設(shè)計(jì)模式之觀察者模式
本文僅是個(gè)人觀點(diǎn),如有錯(cuò)誤請(qǐng)指正
簡(jiǎn)介
當(dāng)對(duì)象間存在一對(duì)多關(guān)系時(shí)暑脆,可以考慮使用觀察者模式(Observer Pattern)赎离。比如,當(dāng)一個(gè)對(duì)象被修改時(shí)气笙,則會(huì)自動(dòng)通知它的依賴對(duì)象。觀察者模式屬于行為型模式怯晕。
實(shí)現(xiàn)
接下來將用三個(gè)例子來說明觀察者模式潜圃,詳細(xì)代碼可以參考文末的地址。
需求
現(xiàn)在有一個(gè)氣象站舟茶,想對(duì)外發(fā)布數(shù)據(jù)谭期,有一個(gè)公司知道后想要通過天氣站的數(shù)據(jù)建立一個(gè)天氣看板,這就交給了公司的開發(fā)小王吧凉,小王拿到需求后隧出,很快就開始行動(dòng)了,下面就是小王的實(shí)現(xiàn)方法
-
氣象站類阀捅,主要提供溫度鸳劳、壓力、濕度參數(shù)也搓,以及數(shù)據(jù)更新,數(shù)據(jù)通知涵紊,再把需要接收參數(shù)的看板加進(jìn)來指定通知對(duì)象傍妒。
public class WeatherData { /** * 溫度 */ private float mTemperate; /** * 壓力 */ private float mPressure; /** * 濕度 */ private float mHumidity; private CurrentConditions mCurrentConditions; /** * 加入需要通知的看板 * @param mCurrentConditions */ public WeatherData(CurrentConditions mCurrentConditions) { this.mCurrentConditions = mCurrentConditions; } public float getTemperature() { return mTemperate; } public float getPressure() { return mPressure; } public float getHumidity() { return mHumidity; } /** * 通知數(shù)據(jù)修改 */ public void dataChange() { mCurrentConditions.update(getTemperature(), getPressure(), getHumidity()); } /** * 數(shù)據(jù)修改 同時(shí)調(diào)用通知方法 * * @param mTemperature * @param mPressure * @param mHumidity */ public void setData(float mTemperature, float mPressure, float mHumidity) { this.mTemperate = mTemperature; this.mPressure = mPressure; this.mHumidity = mHumidity; dataChange(); } }
-
然后就是溫度看板,獲取當(dāng)前的溫度摸柄,兩個(gè)方法颤练,一個(gè)接收數(shù)據(jù)更新,一個(gè)展示數(shù)據(jù)驱负。
public class CurrentConditions { private float mTemperature; private float mPressure; private float mHumidity; public void update(float mTemperature, float mPressure, float mHumidity) { this.mTemperature = mTemperature; this.mPressure = mPressure; this.mHumidity = mHumidity; display(); } public void display() { System.out.println("***Today mTemperature: " + mTemperature + "***"); System.out.println("***Today mPressure: " + mPressure + "***"); System.out.println("***Today mHumidity: " + mHumidity + "***"); } }
-
好了咱們開始測(cè)試一下看看效果如何
public class InternetWeather { public static void main(String[] args) { CurrentConditions mCurrentConditions = new CurrentConditions(); WeatherData mWeatherData = new WeatherData(mCurrentConditions); mWeatherData.setData(30, 150, 40); } }
-
執(zhí)行結(jié)果
***Today mTemperature: 30.0*** ***Today mPressure: 150.0*** ***Today mHumidity: 40.0***
好了看起來需求已經(jīng)滿足了嗦玖,開始使用吧,然而沒過多久跃脊,另外一個(gè)公司也和天氣站聯(lián)系上了宇挫,想要他們提供的天氣數(shù)據(jù)來展示明天的天氣,小王一聽便說那還不簡(jiǎn)單酪术,去weather類里面加上你的看板類器瘪,加上通知翠储,加上。橡疼。
不對(duì)啊援所,這加一個(gè)使用者就這么麻煩了,那以后要是來了更多的使用者怎么辦欣除?又或者有的使用者突然又不使用這個(gè)數(shù)據(jù)了住拭,想要取消通知怎么辦?要是按這樣的話改來改去每次修改都要重新上線历帚,會(huì)非常麻煩滔岳,數(shù)據(jù)的及時(shí)性也得不到保證。小王頭疼不已抹缕,這時(shí)候公司里的老李看到了澈蟆,笑呵呵的說小王,不要著急卓研,我們?cè)僦匦驴纯茨銓懙姆椒ㄅ糠傧胂胄枰尤氲慕涌诤湍阌惺裁垂餐牡胤剑蹅儼阉殡x出去奏赘,然后再寥闪。。磨淌。疲憋。。小王在老李的幫助下很快做出了第二版實(shí)現(xiàn)
-
首先我們加入數(shù)據(jù)提供方接口梁只,將我們之前提到了的缚柳,新增需求方,刪除需求方搪锣,以及通知加入進(jìn)去
public interface Subject { /** * 注冊(cè)觀察者 * @param o */ void registerObserver(Observer o); /** * 移除觀察者 * @param o */ void removeObserver(Observer o); /** * 通知觀察者 */ void notifyObservers(); }
-
接著就是需求方的接口秋忙,主要用來獲取數(shù)據(jù)
public interface Observer { /** * 數(shù)據(jù)更新 * @param mTemperate * @param mPressure * @param mHumidity */ void update(float mTemperate, float mPressure, float mHumidity); }
-
接著修改之前的天氣數(shù)據(jù)類,在這里面我們實(shí)現(xiàn)前面的數(shù)據(jù)提供方的接口构舟,然后重寫里面的方法灰追,注冊(cè)接口我們暫時(shí)使用list作為存儲(chǔ)觀察者的集合,移除接口就是移除集合中的觀察者狗超,這里我們檢查一下觀察者是否存在集合中再移除弹澎,然后是通知接口,我們遍歷集合逐個(gè)通知需求方努咐。(這里我們是將數(shù)據(jù)一個(gè)一個(gè)的傳給使用方)苦蒿。
public class WeatherDataSt implements Subject { private float mTemperate; private float mPressure; private float mHumidity; private List<Observer> mObservers; public WeatherDataSt() { mObservers = new ArrayList<>(); } public float getTemperature() { return mTemperate; } public float getPressure() { return mPressure; } public float getHumidity() { return mHumidity; } public void dataChange() { notifyObservers(); } public void setData(float mTemperate, float mPressure, float mHumidity) { this.mTemperate = mTemperate; this.mPressure = mPressure; this.mHumidity = mHumidity; dataChange(); } @Override public void registerObserver(Observer o) { mObservers.add(o); } @Override public void removeObserver(Observer observer) { if (mObservers.contains(observer)) { mObservers.remove(observer); } } @Override public void notifyObservers() { for (Observer mObserver : mObservers) { mObserver.update(getTemperature(), getPressure(), getHumidity()); System.out.println("開始通知:" + mObserver.getClass().getSimpleName()); } } }
-
天氣看板中實(shí)現(xiàn)觀察者接口,實(shí)現(xiàn)當(dāng)中的數(shù)據(jù)接口
public class CurrentConditions implements Observer { private float mTemperate; private float mPressure; private float mHumidity; @Override public void update(float mTemperate, float mPressure, float mHumidity) { this.mHumidity = mHumidity; this.mPressure = mPressure; this.mTemperate = mTemperate; display(); } public void display() { System.out.println("***Today mTemperate:" + mTemperate + "***"); System.out.println("***Today mPressure:" + mPressure + "***"); System.out.println("***Today mHumidity:" + mHumidity + "***"); } }
-
在查看明天的天氣面板實(shí)現(xiàn)觀察者接口
public class ForcesConditions implements Observer { private float mTemperate; private float mPressure; private float mHumidity; @Override public void update(float mTemperate, float mPressure, float mHumidity) { this.mTemperate = mTemperate; this.mPressure = mPressure; this.mHumidity = mHumidity; display(); } public void display() { System.out.println("**明天溫度:" + (mTemperate + Math.random()) + "**"); System.out.println("**明天氣壓:" + (mPressure + 10 * Math.random()) + "**"); System.out.println("**明天濕度:" + (mHumidity + Math.random()) + "**"); } }
-
然后我們編寫測(cè)試類
public class InternetWeather { public static void main(String[] args) { CurrentConditions mCurrentConditions= new CurrentConditions(); ForcesConditions mForcesConditions = new ForcesConditions(); WeatherDataSt mWeatherDataSt = new WeatherDataSt(); //注冊(cè)天氣看板 mWeatherDataSt.registerObserver(mCurrentConditions); mWeatherDataSt.registerObserver(mForcesConditions); mWeatherDataSt.setData(30, 150, 40); //移除看板 mWeatherDataSt.removeObserver(mCurrentConditions); mWeatherDataSt.setData(40, 250, 50); } }
-
打印結(jié)果
***Today mTemperate:30.0*** ***Today mPressure:150.0*** ***Today mHumidity:40.0*** 開始通知:CurrentConditions **明天溫度:30.708855560149452** **明天氣壓:156.09944964615488** **明天濕度:40.252322896560706** 開始通知:ForcesConditions **明天溫度:40.194891069978894** **明天氣壓:253.89775138044408** **明天濕度:50.95728186211783** 開始通知:ForcesConditions
我們看到經(jīng)過老李的一番指導(dǎo),現(xiàn)在的設(shè)計(jì)基本上滿足了需求渗稍,即使新增看板繼續(xù)繼承觀察者接口刽肠,不需要的觀察者就可以直接移除溃肪,這樣提高了擴(kuò)展性,去除了之前代碼每次新增需要修改代碼的弊端音五,實(shí)現(xiàn)了開閉原則惫撰。
接著老李又說,小王啊躺涝,其實(shí)java內(nèi)置對(duì)象中就已經(jīng)實(shí)現(xiàn)了觀察者模式我們只需要拿來用就可以了厨钻。。坚嗜。
怎么用呢夯膀?請(qǐng)看下面的實(shí)現(xiàn)。
-
天氣數(shù)據(jù)苍蔬,這里我們不再實(shí)現(xiàn)subject接口诱建,反而繼承java內(nèi)置對(duì)象Observable,在內(nèi)置對(duì)象中已經(jīng)幫我們實(shí)現(xiàn)了通知碟绑,注冊(cè)俺猿,移除等方法,所以我們只需要寫我們自定義的方法就行了格仲。
public class WeatherData extends Observable { private float mTemperate; private float mPressure; private float mHumidity; public float getTemperature() { return mTemperate; } public float getPressure() { return mPressure; } public float getHumidity() { return mHumidity; } /** * 通知數(shù)據(jù)已修改 */ public void dataChange() { //Observable的源碼中setChanged()押袍,設(shè)置changed為true表示數(shù)據(jù)已經(jīng)修改,只有當(dāng)changed為true的時(shí)候notifyObservers()方法才會(huì)通知修改 this.setChanged(); this.notifyObservers(new AssembleWeatherData(getTemperature(), getPressure(), getHumidity())); } /** * 修改數(shù)據(jù) * @param mTemperate * @param mPressure * @param mHumidity */ public void setData(float mTemperate, float mPressure, float mHumidity) { this.mTemperate = mTemperate; this.mPressure = mPressure; this.mHumidity = mHumidity; dataChange(); } }
-
同樣天氣看板類只用實(shí)現(xiàn)Observer接口凯肋,重寫當(dāng)中的update方法就行了
public class CurrentConditions implements Observer { private float mTemperate; private float mPressure; private float mHumidity; @Override public void update(Observable arg0, Object arg1) { this.mTemperate = ((AssembleWeatherData) (arg1)).mTemperate; this.mPressure = ((AssembleWeatherData) (arg1)).mPressure; this.mHumidity = ((AssembleWeatherData) (arg1)).mHumidity; display(); } public void display() { System.out.println("***Today mTemperate:" + mTemperate + "***"); System.out.println("***Today mPressure:" + mPressure + "***"); System.out.println("***Today mHumidity:" + mHumidity + "***"); } }
-
另外一個(gè)天氣看板谊惭,一樣的實(shí)現(xiàn)
public class ForecastConditions implements Observer { private float mTemperate; private float mPressure; private float mHumidity; /** * 數(shù)據(jù)更新 同時(shí)調(diào)用展示數(shù)據(jù)方法 * * @param arg0 * @param arg1 */ @Override public void update(Observable arg0, Object arg1) { this.mTemperate = ((AssembleWeatherData) (arg1)).mTemperate; this.mPressure = ((AssembleWeatherData) (arg1)).mPressure; this.mHumidity = ((AssembleWeatherData) (arg1)).mHumidity; display(); } /** * 展示數(shù)據(jù) */ public void display() { System.out.println("**明天溫度:" + (mTemperate + Math.random()) + "**"); System.out.println("**明天氣壓:" + (mPressure + 10 * Math.random()) + "**"); System.out.println("**明天濕度:" + (mHumidity + Math.random()) + "**"); } }
-
為了方便傳遞數(shù)據(jù),我們?cè)傩陆ㄒ粋€(gè)AssembleWeatherData類侮东,用于設(shè)計(jì)返回?cái)?shù)據(jù)內(nèi)容
public class AssembleWeatherData { public float mTemperate; public float mPressure; public float mHumidity; public AssembleWeatherData(float mTemperate, float mPressure, float mHumidity) { this.mTemperate = mTemperate; this.mPressure = mPressure; this.mHumidity = mHumidity; } }
-
接著我們新建測(cè)試類
public class InternetWeather { public static void main(String[] args) { CurrentConditions mCurrentConditions; ForecastConditions mForecastConditions; WeatherData mWeatherData; mCurrentConditions = new CurrentConditions(); mForecastConditions = new ForecastConditions(); mWeatherData = new WeatherData(); //java內(nèi)置觀察者模式中的addObserver方法圈盔,用于注冊(cè)觀察者 mWeatherData.addObserver(mCurrentConditions); mWeatherData.addObserver(mForecastConditions); mWeatherData.setData(30, 150, 40); //內(nèi)置方法,用于移除觀察者悄雅,重新修改數(shù)據(jù) mWeatherData.deleteObserver(mCurrentConditions); mWeatherData.setData(35, 150, 60); } }
-
打印數(shù)據(jù)
**明天溫度:30.676433372455936** **明天氣壓:154.2686999140749** **明天濕度:40.015042461544155** ***Today mTemperate:30.0*** ***Today mPressure:150.0*** ***Today mHumidity:40.0*** **明天溫度:35.4090594721898** **明天氣壓:157.28459919820716** **明天濕度:60.286781430877525**
果然經(jīng)過老李一番重構(gòu)驱敲,小王很快就明白了,觀察者模式的好處煤伟,看著老李地中海的腦袋心想,這或許就是成為大牛的代價(jià)吧木缝。便锨。。我碟。
總結(jié)
前面講述了兩種觀察者模式的實(shí)現(xiàn)方法放案,一個(gè)是我們自己寫的接口做的實(shí)現(xiàn),另一種是直接使用java中的內(nèi)置觀察者模式實(shí)現(xiàn)矫俺,兩者各有各的優(yōu)勢(shì)和缺點(diǎn)吱殉,對(duì)于一般的情況內(nèi)置觀察者就能滿足掸冤,但是有一點(diǎn)Observable是一個(gè)類不是接口所以只能繼承他,但由于java只能對(duì)接口多繼承友雳,所以使用內(nèi)置對(duì)象的時(shí)候需要多繼承的時(shí)候就不能使用了稿湿,自定義的方法可以更靈活,但是需要寫更多的代碼押赊,魚和熊掌不可兼得饺藤。
鏈接
本文代碼倉(cāng)庫(kù)地址:https://gitee.com/singlekingdom/JavaDesignPatterns.git