觀察者模式 嗨,大家好,我們又見面了,不知道上次的籃球比賽大家是不是還意猶未盡呢,沒看到的同學可以前往https://www.cnblogs.com/MrJR/p/10441166.html觀摩哦。 今天呢,我們來看另一個Java設計模式——觀察者模式。 顧名思義,觀察者模式,那肯定要有觀察者,既然 ...
觀察者模式
聲明:本文為原創,如有轉載請註明轉載與原作者並提供原文鏈接,僅為學習交流,本人才識短淺,如有錯誤,敬請指正
嗨,大家好,我們又見面了,不知道上次的籃球比賽大家是不是還意猶未盡呢,沒看到的同學可以前往https://www.cnblogs.com/MrJR/p/10441166.html觀摩哦。
今天呢,我們來看另一個Java設計模式——觀察者模式。
顧名思義,觀察者模式,那肯定要有觀察者,既然有了觀察者,肯定就有被觀察者,當然如果你說你就是喜歡發呆,啥都不觀察,當我沒說。
不能免俗,我們首先來看一下觀察者模式的官方定義:
觀察者模式:定義了對象之間的一對多依賴,當一個對象改變狀態時,他的所有依賴者都會收到通知並自動更新。
或許大家覺得這段話還蠻好理解的,畢竟沒啥專業術語,當一個對象改變了,他的依賴者都會收到通知:喂,我被改變了,你們也要快快行動起來啦,問題的關鍵在於,如何保證通知的靈活性。
不是所有的觀察者都想一直目不轉睛盯著同一個東西,拜托,那會把人逼瘋的,也會有人想一起加入觀察的行列,有什麼好東西呀,讓我康康,如果我們把觀察者與被觀察者聯繫的過於緊密,意味著每次做出改變都是一個浩大的工程,我們要反覆修改代碼,實現對不同的觀察者的通知,還記得我們的原則嗎,對擴展開放,對修改關閉。
好啦,我們先把這些苦惱丟到一邊,來看一下我們的新朋友,小王同學與小楊同學,她們可是一個十足的購物狂,每次逛街都能買好多的東西,最近呢,他們卻在為購物而煩惱,這是為什麼呢?我們來聽聽她們怎麼說的。
楊:雙十一馬上就要到了,我在購物網站看中了一款BB霜和一款新手機。
王:真巧,我也看中了這兩樣東西,咱們一起買沒準可以打折喲!
楊:買什麼呀買,根本就搶不到,每次等我反應過來的時候,早就賣光了!!
王:這倒是個問題,我也經常搶不到自己想要的東西。
楊:要是商品剛上架,我們就能接受到通知就好了。
王:這樣我們肯定就能搶到了。
作為一名程式員,我們是否應該立刻給兩位可愛的女士伸出援手呢,當然是啦,而在這裡,觀察者模式就可以大顯身手了。
我們可以看出,此處的觀察者就是小楊與小王,而主題呢,我這裡使用了主題二字,其實就是被觀察者,這是觀察者模式中的慣用說法,主題就是購物網站,而通知就是新商品的上架,最後觀察者的改變便是小楊與小王的買買買。
我們首先定義一個觀察者介面,這個介面定義了觀察者在收到通知後應該執行的方法
public interface Person { void buy(String productionName); }
此處當然就是買買買的方法啦,我們會傳入一個商品名。
接下來我們定義主題介面
public interface ShoppingApp { void registerPerson(Person person); void removePerson(Person person); void notifyPerson(); }
主題介面就是購物APP,它有三個方法,相信通過方法名大家也能看出來它們的作用,分別是註冊觀察者,移除觀察者與通知觀察者。
然後,我們實現一個具體的購物網站。
public class TaoBao implements ShoppingApp { private LinkedList<Person> persons; private String productionName; public TaoBao(){ persons = new LinkedList<>(); } @Override public void registerPerson(Person person) { persons.add(person); } @Override public void removePerson(Person person) { if (persons.contains(person)){ persons.remove(person); }else{ System.out.println("錯誤操作,無法刪除不存在的用戶"); } } @Override public void notifyPerson() { for(Person person : persons){ person.buy(productionName); } } public void setProductionName(String productionName){ this.productionName = productionName; notifyPerson(); } }
我們可以看到,在這個超迷你淘寶中,我們維護了一個鏈表,而這個鏈表儲存的就是所有正在觀察這個主題的觀察者們,當然,我們初始化時把他置空。
我們實現註冊觀察者,直接將觀察者對象添加進我們的觀察者列表即可。
我們實現移除觀察者,將觀察者從鏈表中移除即可。
我們重點看一下通知方法的實現以及通知方法如何被調用,我們可以看到,為了通知每個觀察者,我們遍歷了所有的觀察者們,然後調用了他們應對通知的方法,以表示他們已經接收到通知並做出了積極的回應,註意,我們一直使用的都是介面而不是具體類,我們只需要調用buy方法即可,根本不用去管到底是什麼觀察者,不管你是張三李四王五還是蜘蛛俠超人,反正我知道你肯定有buy這個方法,因為你實現了Person介面,這種針對介面編程而不是針對實現編程使我們的代碼完全與具體類解耦,我們不需要去管到底是什麼類在與我交互,畢竟如果你連這個介面都沒實現,編譯器都不會放過你的(笑)。
而在這個類里,我們何時通知觀察者取決於我們的商品是否上架,即productionName是否被設置了值,當淘寶商品上架,我們就會調用通知方法,通知所有的觀察者。
接下來,我們讓小王同學和小楊同學現身。
public class MissW implements Person { @Override public void buy(String productionName) { System.out.println("王小玲同學:"+productionName + "終於開售了,還好提前關註了!"); } }
public class MissY implements Person { @Override public void buy(String productionName) { System.out.println("楊小青同學:我要的" + productionName + "終於有貨了,我要趕緊搶!!"); } }
可以看出來,具體的觀察者實現觀察者介面後,只要按照自己的需求實現介面方法即可,在這裡,我們只是簡單的列印出一句話。
最後呢,當然就是我們的雙十一登場啦,小楊同學與小王同學早就按捺不住激動的心情,要搶購了,雙十一將檢測我們的代碼到底有沒有起到作用。
public class Double11 { public static void main(String[] args) { TaoBao taoBao = new TaoBao(); MissW wang = new MissW(); MissY yang = new MissY(); taoBao.registerPerson(wang); taoBao.registerPerson(yang); taoBao.setProductionName("雅詩蘭黛高級BB霜"); taoBao.setProductionName("華為Mate20 pro"); taoBao.removePerson(wang); taoBao.setProductionName("悅詩風吟高級化妝禮盒"); } }
我們首先創建了超迷你淘寶對象作為具體主題,然後創建了小王同學與小楊同學作為具體觀察者,接著小王與小楊就註冊了淘寶,等著心儀的物品擺上貨架,接收通知。
12點的鐘聲剛過,他們心儀的東西終於擺上了貨架,我們來看運行結果,她們到底有沒有接收到通知呢?
看來我們的小楊同學與小王同學都成功收到了淘寶發出的通知,在搶購了自己喜歡的東西之後,小王同學退出了淘寶,不再接收淘寶發出的通知,更不會再去買東西,而小楊同學仍然能接收到淘寶發出的通知而繼續購物。
觀察者模式可以在不影響原主題通知與觀察者更新方法的基礎上,新增或移除觀察者,對擴展的開放,對修改的關閉的設計原則再一次展現了它強大的力量,同時我們還發現針對介面編程可以讓我們六根清凈,不必再被具體類所糾纏,這也是解耦的力量體現。
我們還要註意一件事,在購物的例子中,超迷你淘寶在商品上架後,主動給所有的觀察者“推送”了消息,觀察者則被動的接收消息,不管這個消息它是否需要(在我們的例子中,預設觀察者收到的消息都是她們需要的),有可能觀察者雖然關註了主題,但是只要主題的某些消息,那麼這裡就涉及到了另一種接收消息的方式——觀察者主動”拉“消息,通過主題的getter方法,觀察者可以主動去主題拉取自己需要的消息,根本應用場景的不同,兩種方式各有千秋,大家可以自己動手實現一下試試。
好的,伴隨著兩位美麗的女士錢包空空,我的淺談觀察者模式也就先到這裡了,我們下次再見哦。