觀察者模式又叫發佈訂閱模式,有訂閱者和發佈者;發佈者能夠包含了多個訂閱者訂閱的事件,一旦發佈者執行,會執行全部的訂閱者訂閱的事件。我以爲這麼講仍是很迷糊。其實就是說「發佈者」是一段上層代碼,他知道他所須要執行的過程當中會發生一些事情,而這些事情具體邏輯本身又不知道,就算知道全部的邏輯,要用條件分支判斷執行,這總歸的是很差的,因此纔有了這個模式。這是一個很是棒的模式。他使得發佈者的代碼保持不變。而訂閱者的事件能夠散步在他們本身的代碼中。前端
咱們實際應用中最多見的就是頁面中的按鈕點擊事件。當咱們雙擊webform中的按鈕後會自動生成一個btn_OnClick的方法,而後在裏面編寫一些邏輯,同時也生成了btn.Click+= new EventHandler(btn_OnClick)代碼(只是2.0以後這個代碼就被隱藏起來了),這就是給按鈕btn(訂閱者)訂閱了一個事件。這些邏輯理當屬於按鈕所在的頁面,而不是須要執行這個方法的代碼中。web
當按鈕點擊以後,會觸發頁面的提交,webform框架能夠獲取是哪一個按鈕被點擊過,而後執行btn.Click(),就能夠執行咱們具體的邏輯了。 設想若是不用這個模式,按鈕的Click方法是否是要寫不少switch來判斷是哪一個按鈕,而後調用該有的邏輯。ajax
那麼假如說咱們不用.NET的這套事件機制,該如何漂亮的抽象出Click的代碼呢?瀏覽器
其實只要至關上一節的策略模式,咱們只要給Click接受一個IClickEvent接口,而後button類再包含一組IClickEvent的成員,就能夠遍歷這些成員執行了。訂閱的代碼就變成了button.AddEvent(new xxClickEvent());便可。框架
在.net中,咱們不必用IClickEvent接口的形式,由於咱們有委託這個方法代理(或者叫方法指針),他能夠說是一個只具備一個方法的接口,而.net中的事件自己也是委託。只是事件形式的委託是封閉的,不可在外部直接賦值操做,只能訂閱和刪除訂閱。函數
綜上來看,觀察者模式不過是一個處理未知方法的模式,他漂亮的把具體邏輯分散到他該屬於地方。.net
在web前端的Javascript中,這種狀況就更爲廣泛。好比咱們用jQuery時,給一個按鈕增長一個onClick方法,只須要$(「#btn」).click(function(){})便可。瀏覽器會知道具體的哪一個按鈕被點擊,甚至咱們隨便點擊頁面的一個地方,都會被瀏覽器截獲,假如咱們有相應的OnClick方法,他會執行調用,並傳值給咱們當前鼠標的位置等。 在咱們發起一個ajax請求時,會有一個參數是callback方法,在判斷完XmlHttpRequest的readyState == 4後調用,每一個ajax的請求完後的callback都不一致。因此說他也是觀察者模式。咱們說這種叫作回調模式是否是更好?所謂的「回」就是使用以前的代碼,而不是當前的代碼。代理
因此說委託或者回調(方法指針)也是一種抽象,在不具有這種能力的語言裏可用接口來代替。重複上節的話,抽象提取變化事物的共性,不論是面向對象仍是過程式仍是函數式,都離不開抽象的思想。指針
說了這麼多,不知道表達清楚沒有,咱們來說一個實際應用不依賴於框架的。orm
好比咱們發佈一篇文章,經常使用邏輯就是保存文章。若是哪天來了新的需求,好比說跟某某公司合做,發表完文章以後須要給用戶增長一些獎勵。又過了幾天又來一個新的需求,所最近抓的緊,須要對文章審覈,一旦有違禁的關鍵詞不容許發佈。這兩個邏輯以前都不存在。咱們是否是要修改代碼呢?這兩個需求都是臨時的。假如修改的保存邏輯,回頭合做取消和風頭事後又要取消掉。這太不合適了。咱們應讓代碼的修改的範圍縮到最小。
若是咱們在保存邏輯先後增長事件,好比PreSave和EndSave,而後對應增長相關的訂閱代碼,就不須要修改Save的邏輯。可是調用Save的代碼依然須要增長兩個方法和訂閱代碼。
public class ArticleController : Controller { public ActionResult Save(Article article) { var articleManager = new ArticleManager(); articleManager.PreSaveEvent += x => { var shouldBlock = BlockWords.Filter(article); if(shouldBlock) { throw new SecretExcepetion("對不起,您的文章中包含違禁關鍵詞,請檢查"); //封殺用戶 } }; articleManager.EndSaveEvent += x=> { //獎勵用戶 } articleManager.Save(article); } } public class ArticleManager { public event Action<Article> PreSaveEvent; public event Action<Article> EndSaveEvent; public void Save(Article article) { if(PreSaveEvent!=null) { PreSaveEvent(article); } var db = new ArticleRepository(); db.Save(article); if(EndSaveEvent!=null) { EndSaveEvent(article); } } } public class ArticleRepository { public void Save(Article article) { database.Save(article); } }
雖然上面的用的是事件的方式,若是用委託做爲Save的參數也能夠,只是做爲參數對之後的重構可能會帶來麻煩。
這樣只須要修改Controller的代碼就能改變這些需求,若是不連Controller的代碼也不想改怎麼辦呢?後面再說吧。