小看--发布-订阅(观察者)模式
? ? (一)什么是觀察者模式
? ? ? ?發布-訂閱,這兩個詞語是對觀察者的最好解釋,現實生活中,這樣的案例有很多,比如在籃球比賽過程中教練,喊一個暫停,然后球員和裁判都做相關的響應,還有比如OA里面發布的放假通知等等。無論是籃球比賽,還是OA的通知,都存在一個角色,它的作用就是保持對相關問題的關注,在問題發生變化的時候,是Ta把消息通知給相關各方。觀察者模式也差不多這樣,它抽象一類對象(觀察者)專門負責"盯著"目標對象,當目標對象狀態有變動的時候,每個觀察者就會獲得通知并迅速做出響應,觀察者模式解決的也是調用通知關系所帶來的依賴。
? ? ? ? 上面的概念是比較官方的,我喜歡用自己的語言來講解,比如現實生活中的,蝴蝶效應,我們程序里面的,下訂單,就要發短信,減少庫存呀,等等一系列的連鎖反應,一句話,就是某個人發出一個行為,會引起一系列的連鎖反應。
? ? ?( 二 ) 一個簡單的案例
? ? ? ?南美洲亞馬孫河邊熱帶雨林中的蝴蝶,偶爾扇幾下翅膀,就有可能在兩周后引起美國得克薩斯的一場龍卷風。原因在于:蝴蝶翅膀的運動,導致其身邊的空氣系統發生變化,并引起微弱氣流的產生,而微弱氣流的產生又會引起它四周空氣? ? ? ? ?或其他系統產生相應變化,由此引起連鎖反應,最終導致其他系統的極大變化。“蝴蝶效應”聽起來有點荒誕,但說明了事物發展的結果,對初始條件具有極為敏感的依賴性;初始條件的極小偏差,將會引起結果的極大差異。這就是蝴蝶效應,下面我們用程序來模擬整個過程。
? ? ? ?假設蝴蝶煽動翅膀,可以引起雞飛狗跳,老鼠出洞,天氣變化,颶風形成。
/// <summary>/// 蝴蝶/// 假設蝴蝶煽動翅膀,可以引起雞飛狗跳,老鼠出洞,天氣變化,颶風形成/// </summary>public class ButterFly {/// <summary>/// 蝴蝶起飛/// </summary>public void Fly(){Console.WriteLine("蝴蝶起飛啦");Console.WriteLine("---------------產生連鎖反應---------------");new Chicken().Fly();new Dog().Jump();new Mouse().GoOut();new Weather().Change();new Wind().Generate();Console.WriteLine("---------------蝴蝶效應結束---------------");}}? ? ? 這就是我們蝴蝶類,它起飛會造成這么多影響,會和這么多類產生關聯(耦合太高了)。如果有一天說要增加一個老虎上山了呢,或者說要移除一個,或者說改變一下順序,現在我們就要改變我們的蝴蝶類。我們來分析造成這個的原因是為啥呢,因為這個類違法了單一職責的原則,蝴蝶飛就可以了,那為啥還要去管飛完之后產生的連鎖反應呢,那行我們知道了這個就是不穩定的原因,那接下來開始我們的耍鍋套路,把不是自己的東西丟出去,做好自己的事情就可以了。
? ? ?那怎么丟呢,我們都應該要開發一個接口給別人添加關聯進來, 因為這里邊有很多類,并且都沒有啥關聯,所以只能才有一個接口,把他們都聯系起來。所以我們接下來定義一個接口IObserver,讓雞,狗,天氣等等類實現這個接口。
public interface IObserver{void Action();}? ? ?雞類
public class Chicken:IObserver {public void Fly(){Console.WriteLine("雞雞起飛了");}public void Action(){Fly();}}? ? ?當所有類實現這個接口的時候,我們就可以在蝴蝶類里面這樣定義了;先給定義一個集合和添加觀察者的方法,用來存儲觀察者的集合。? ??
private List<IObserver> _objServers=new List<IObserver>();public void AddObserver(IObserver item){this._objServers.Add(item);}/// <summary>/// 蝴蝶起飛/// </summary>public void Fly(){foreach (var obj in _objServers){obj.Action();}}? ? ? 我們在客戶端調用呢
ButterFly butterFly=new ButterFly();butterFly.AddObserver(new Chicken());butterFly.AddObserver(new Dog());butterFly.AddObserver(new Mouse());butterFly.AddObserver(new Weather());butterFly.AddObserver(new Wind());butterFly.Fly();? ? ?可能很多同學還是會說,這里還是有依賴,對確實的,但是我們把依賴交給了上游,上游改變不會影響下游,也就是蝴蝶類。
? ? ?從上面這個例子,我們看出觀察者模式,有三個東西,第一:引起連鎖反應的類(主題) 第二:有一個接口,實現了這個接口都是可以成為觀察者的,第三:觀察者。
? ? (三)觀察者模式的演變
? ? ? ? 下面請看代碼。有三個類,A,B,C想接收來自X的消息,簡單的說X發生變化,他們也跟著變化。
public class A {public int Data;public void Update(int data) {this.Data = data;}}public class B {public int Count; public void NotifyCount(int data) { this.Count = data; } } public class C { public int N; public void Set(int data) { this.N = data; } } //上面這三個類,類A,B,C是希望獲得X通知的類型 public class X { private int data; public A InstanceA; public B InstanceB; public C InstanceC; public void SetData(int data) { this.data = data; InstanceA.Update(data); InstanceB.NotifyCount(data); InstanceC.Set(data); } }在我們調用的時候,應該這么做
A a = new A();B b = new B();C c = new C();X x = new X();x.InstanceA = a;x.InstanceB = b; x.InstanceC = c; x.SetData(10); Console.WriteLine("x發出消息了"); Console.WriteLine(a.Data); Console.WriteLine(b.Count); Console.WriteLine(c.N); 對于上面這種做法,大家應該都會。下面是這個上面例子的類圖關系,從這里,我們可以看得X和太多類直接關聯了。已經很不符合面向對象的設計原則了。
? ? ? ? 經過觀察,我們發現類A,B,C都有個共同之處,都是有一個類似于更新的方法,如果對他們進行抽象的話呢,就可以讓類X僅僅依賴于一個抽象的類型。下面直接看代碼
public interface IUpdatableObject {int Data {get;}void Update(int newData);}public class A : IUpdatableObject {private int data;public int Data { get { return this.data; } } public void Update(int newData) { this.data = newData; } } public class B : IUpdatableObject { private int data; public int Data { get { return this.data; } } public void Update(int newData) { this.data = newData; } } public class C : IUpdatableObject { private int data; public int Data { get { return this.data; } } public void Update(int newData) { this.data = newData; } } public class X { private IUpdatableObject[] objects = new IUpdatableObject[3]; //這個是索引器的用法 public IUpdatableObject this[int index] { set { objects[index] = value; } } private int data; public void Update(int newData) { this.data = newData; foreach(IUpdatableObject obj in objects) { obj.Update(newData); } } }//調用代碼如下
X x = new X();IUpdatableObject a = new A();IUpdatableObject b = new B();IUpdatableObject c = new C();x[0] = a;x[1] = b; x[2] = c; x.Update(10); Console.WriteLine("x發出了消息"); Console.Write(a.Data); Console.Write(b.Data); Console.Write(c.Data);
通過上面我們就可以依賴于抽象了,比如當我們要新增加一個訂閱者的時候,也不用去類X里面改內部代碼,請看類圖。
? ? ?下面開始我們的版本的三;
先看看這幅圖,以后我們把消息的發布者,叫做主題,接收著叫做觀察者。主題類似于前文的X類,觀察者類似于A,B,C類。
? ? ?第二版本的時候,我們已經解決了兩個對象之間的松耦合,關于觀察者的一切,主題只需要知道他實現了哪個接口(前文中的IUpdatableObject接口),只要是實現了這個接口的,主題就會把他認為是觀察者。
? ???繼續這觀察者模式的發展和演變,到了我們第三個版本的觀察者模式。到了這步,我們想到如果有觀察者要退出,不訂閱這個主題了,等等操作(當主題發生變化的時候,,我們應該怎么做)。根據我們的面向對象的經驗,我們應該很容易想到應該把主題對象也要抽象化。
? ? ?
? ? ? ??
public interface ISubject {int Data { get; set; } //這個Data就是類似于用來發送消息的。/// <summary>/// 追加/// </summary>/// <param name="obs"></param>void Attach(IObserver obs);/// <summary>/// 刪除/// </summary>/// <param name="obs"></param>void Detach(IObserver obs);/// <summary>/// 監聽/// </summary>void Notify();}public interface IObserver {int Data { get; set; }void Update(int n);}public class ConcreteSubjectA : ISubject {public int Data { get; set; }public List<IObserver> ObsList = new List<IObserver>();public void Attach(IObserver obs) {Console.WriteLine("添加觀察者成功");ObsList.Add(obs);}public void Detach(IObserver obs) {Console.WriteLine("刪除觀察者成功");ObsList.Remove(obs);}public void Notify() {foreach(var obs in ObsList) {obs.Update(this.Data);}}}public class ConcreteObserverA : IObserver {public int Data { get; set; }public void Update(int n) {this.Data = n;}}public class ConcreteObserverB : IObserver {public int Data { get; set; }public void Update(int n) {this.Data = n;}}public class ConcreteObserverC : IObserver {public int Data { get; set; }public void Update(int n) {this.Data = n;}} ISubject csA = new ConcreteSubjectA();IObserver a = new ConcreteObserverA();IObserver b = new ConcreteObserverB();IObserver c = new ConcreteObserverC();csA.Attach(a);csA.Attach(b);csA.Attach(c);csA.Data = 20;//移除觀察者csA.Detach(b);csA.Notify();//我們現在做的這個版本相對于上一個版本就是增加了對觀察者的管理。//以及我們可以讓我們的程序都是依賴于抽象。Console.WriteLine($"類A的數據為:{a.Data}");Console.WriteLine($"類B的數據為{b.Data}");Console.WriteLine($"類C的數據為{c.Data}"); 降到這里基本上把觀察者模式給講完了,我更加喜歡叫做發布訂閱模式,這樣更加好理解。? ? ? 我們上面的那個例子還可以在抽象一點,利用泛型類和泛型方法來做,真正的達到抽象。下面是最終版本的實現代碼(把泛型類和泛型方法用上),這個大家可以思考下怎么做?相關代碼暫時不貼出來先。?
? ? ? 最后,希望大家能夠帶帶我這個菜鳥,謝謝各位。
? ? ??
轉載于:https://www.cnblogs.com/gdouzz/p/6955825.html
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的小看--发布-订阅(观察者)模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 自己动手实现一个MVVM库
- 下一篇: Entity Framework 基础