上一篇說到了觀察者模式較爲傳統的用法,這篇準備分享點流行的,不過在開始新內容以前,咱們不妨先思考一下兩種場景,一個是報社訂閱報紙,另外一個是在黑板上發公告,都是典型觀察者模式應用場景,兩者有何不一樣?git
能夠看到,兩者有明顯的區別。前者,觀察者必需要註冊到被觀察者上才能接收通知;然後者,觀察者和被觀察者之間是相互徹底陌生的。回顧一下咱們在上一篇中舉的例子,不難發現它其實相似第二種場景,狗叫並不知道誰會聽見,而聽的人也不是爲了聽狗叫,他僅僅是在關注外界的動靜,剛好聽到了狗叫而已。但咱們採用的是相似第一種場景的處理方式,顯然並不合適。所以,也就天然而然的留下了兩個問題:github
dog.AddObserver(...)
真的合適嗎?實際生活中,狗真的有這種能力嗎?C#
中不支持多繼承,若是Dog
自己繼承自Animal
的基類,若是同時做爲被觀察者,除了用上述演進一的實現,還能如何實現?針對這兩個問題,該怎麼解決了?不妨再回顧一下以前學過的設計原則,看看哪裏能夠尋找突破口。編程
一番思索不難發現,主題類違背了合成複用原則,也就是咱們常說的,HAS A
比IS A
更好。既然知道HAS A
更好,咱們爲何非得經過繼承來實現功能的複用呢?更況且咱們繼承的仍是個普通類。設計模式
基於這種思路,咱們能夠試着把繼承改爲組合,不過在這以前,咱們不妨一步到位,乾脆再爲Subject
類定義一個抽象的接口,省得看着不舒服,畢竟面向抽象編程嘛:網絡
public interface ISubject { void AddObserver(IObserver observer); void RemoveObserver(IObserver observer); void Publish(EventData eventData); } public class Subject: ISubject { private readonly IList<IObserver> _observers = new List<IObserver>(); public void AddObserver(IObserver observer) { _observers.Add(observer); } public void RemoveObserver(IObserver observer) { _observers.Remove(observer); } public void Publish(EventData eventData) { foreach (var observer in _observers) { observer.Update(eventData); } } }
邏輯並無任何改動,僅僅是實現了一個接口而已,這一步不作其實也沒有關係。接下來該作什麼應該也很清楚了,沒錯,就是組合到被觀察者中去,也就是Dog
和Son
,下面是具體的實現:架構
public class Dog { private readonly ISubject _subject; public Dog(ISubject subject) { this._subject = subject; } public void Bark() { Console.WriteLine("遙聞深巷中犬吠"); _subject.Publish(new EventData { Source = this, EventType = "DogBark" }); } } public class Son : IObserver { private readonly ISubject _subject; public Son(ISubject subject) { this._subject = subject; } public void Update(EventData eventData) { if (eventData.EventType == "DogBark") { Wakeup(); } } public void Wakeup() { Console.WriteLine("既而兒醒,大啼"); _subject.Publish(new EventData { Source = this, EventType = "SonCry" }); } }
修改的僅僅是被觀察者,觀察者不須要作任何改變。看到上面的調用,不知道你們有沒有一種熟悉的感受呢?沒錯,這裏的使用方式像極了微服務中經常使用的事件總線EventBus
,事實上,事件總線就是這麼實現的,基本原理僅僅是觀察者模式繼承轉組合而已。框架
再看看調用的地方:微服務
static void Main(string[] args) { ISubject subject = new Subject(); Dog dog = new Dog(subject); Wife wife = new Wife(); Husband husband = new Husband(); Son son = new Son(subject); subject.AddObserver(wife); subject.AddObserver(husband); subject.AddObserver(son); dog.Bark(); }
將Dog
與Subject
之間的關係改成HAS A
以後,實際的事件發出者和事件接收者之間多了一層,使得兩者之間徹底解耦了。這時,Dog
能夠繼承本身的Animal
基類了,而且也不用再作相似在Dog
類中管理Wife
、Husband
、Son
這麼奇怪的事了,對觀察者的管理交給總線來完成。this
再來看看這時的類圖長什麼樣子:spa
若是以爲複雜,能夠不看Dog
和Sun
這兩個節點,只看實線框中的部分,有沒有發現就是前面簡易版的觀察者模式呢?被觀察者仍是Subject
,只不過和Dog
、Sun
已經沒什麼關係了,這是多一層必然會致使的結果。到這裏,其實已經完美實現需求了,Subject
是原來的被觀察者,但如今至關於事件總線,在程序啓動的時候,將觀察者所有註冊到總線上就能夠接收到總線上的事件消息了。
你覺得這樣就完了嗎?其實並無。再回到軟件開發領域,咱們知道,事件的觸發能夠發生在系統內部,也能夠發生在系統之間。而前面不管哪一種方式的實現,其實解決的都是內部問題,那若是須要跨系統該怎麼辦呢?直接調用的話,會像上篇當中的第一個實現同樣,出現強耦合,只不過這時調用的再也不是普通的方法,而是跨網絡的API,而強耦合的也再也不是類與類之間,而是系統與系統之間。而且隨着事件數量的增多,也會使得調用鏈變得混亂不堪,難以管理。
爲了解決這個問題,就須要在全部系統以外,加入一箇中間代理的角色,全部發布者將事件消息按不一樣主題發送給代理,而後代理再根據觀察者關注主題的不一樣,將消息分發給相應的觀察者,固然,前提是發佈者和觀察者都提早在代理這裏完成註冊登記。
咱們先模擬實現一個代理,固然,我這裏只是經過單例模式實現一個簡單的示例,真實狀況會比這個複雜的多:
public class Broker { private static readonly Lazy<Broker> _instance = new Lazy<Broker>(() => new Broker()); private readonly Queue<EventData> _eventDatas = new Queue<EventData>(); private readonly IList<IObserver> _observers = new List<IObserver>(); private readonly Thread _thread; private Broker() { _thread = new Thread(Notify); _thread.Start(); } public static Broker Instance { get { return _instance.Value; } } public void AddObserver(IObserver observer) { _observers.Add(observer); } public void RemoveObserver(IObserver observer) { _observers.Remove(observer); } private void Notify(object? state) { while (true) { if (_eventDatas.Count > 0) { var eventData = _eventDatas.Dequeue(); foreach (var observer in _observers) { observer.Update(eventData); } } Thread.Sleep(1000); } } public void Enqueue(EventData eventData) { _eventDatas.Enqueue(eventData); } }
這裏經過單例模式定義了一個Broker
代理類,實際狀況下,這部分是由一個永不停機的MQ服務承擔,主要包括四個部分組成:
Queue<EventData>
類型的隊列,用於存放事件消息;事實上,上述四個部分都應該針對不一樣的主題實現,也就是咱們經常會提到的Topic,幾乎全部的MQ都會有Topic的概念,爲了簡單,咱們這裏就不考慮了。
再來看看Subject
的實現:
public interface ISubject { void Publish(EventData eventData); } public class Subject: ISubject { public void Publish(EventData eventData) { Broker.Instance.Enqueue(eventData); } }
因爲對IObserver
的管理交給了Broker
代理,所以這裏就不須要再關注具體的觀察者是誰,也不須要管理觀察者了,只須要負責發佈事件就好了。須要注意的是,事件消息發佈給了Broker
,後續的一切工做交給Broker
全權處理,觀察者依然不須要作任何代碼上的修改。
調用的地方涉及到的改變主要體如今觀察者的註冊上,畢竟管理者再也不是Subject
,而是交由Broker
代理接管了:
static void Main(string[] args) { ISubject subject = new Subject(); Dog dog = new Dog(subject); Wife wife = new Wife(); Husband husband = new Husband(); Son son = new Son(subject); Broker.Instance.AddObserver(wife); Broker.Instance.AddObserver(husband); Broker.Instance.AddObserver(son); dog.Bark(); }
乍一看,事情變得愈來愈複雜了,這裏爲了解決跨系統的問題,又套了一層,類圖有點複雜,爲避免混亂,我就不畫了。不過好在思路的演進是清晰的,達到如今的結果,應該也不會以爲突兀,這個其實就是當前盛行的MQ的基本實現思路了。
經過前面一系列的改造,咱們解決了不一樣場景下的事件處理問題。接下來,咱們再次梳理一下觀察者模式的整個演進過程,先看一張圖:
這張圖顯示了觀察者模式演進的不一樣階段,主題與觀察者之間的調用關係:
能夠看出,他們都有各自的應用場景,並不能簡單的說誰更先進,誰能替代誰。能夠預見,觀察者模式將來可能還會繼續演進,去應對更多新的更復雜的場景。
既然觀察者模式這麼好用,那.Net框架中天然也會內置一些處理機制了。
delegate
)和事件(event
)就是觀察者模式的很好的一種實踐,不過須要注意的是,委託和事件,嚴格意義上講,已經不能稱之爲設計模式了,由於它們針對的都是方法,跟面向對象設計無關,不過卻是能夠稱之爲慣用法。不過無論怎麼樣,它們要解決的問題跟觀察者模式是一致的。IObserver<T>
和IObservable<T>
可用於實現事件通知機制,顧名思義,前者至關於觀察者,後者至關於主題。這裏就不列代碼,以避免喧賓奪主了,由於這不是本文的重點。並且前者太經常使用了,應該沒什麼人不會。然後者呢,不知道你們用的多很少,但其實我本身沒怎麼用,我更願意根據不一樣的場景來定義語義更明確的接口,如ISender
用於發送,IProducer
用於生產,IListener
用於監聽,IConsumer
用於消費等。
事件無處不在,絕不誇張的說,整個世界的運轉都是由事件驅動的。所以觀察者模式也是無處不在的。咱們知道,設計模式通過這麼多年的發展,已經有了很大的變化,有的下沉變成了某些語言的慣用法,例如後面會講到的迭代器模式,有些上升更偏向於架構模式,例如前面講過的外觀模式。甚至有的被淘汰,例如備忘錄模式。可是觀察者模式倒是惟一一個向上可用於架構設計,向下被實現爲慣用法,中間還能重構代碼,簡直無處不在,無所不能。而且能夠預見,將來也必然是經久不衰。
說的有點誇張了,不過也確實說明觀察者模式再怎麼重視也不爲過了!