一、引言
相信猿友都大大小小經歷過一些面試,其中有道經典題目,場景是貓咪叫了一聲,老鼠跑了,主人被驚醒(設計有擴展性的可加分)。對于初學者來說,可能一臉懵逼,這啥跟啥啊是,其實博主當年也這感覺,O(∩_∩)O哈哈~好了,廢話不多說,今天我們要學習的內容就是要解決這種業務場景——觀察者模式,又叫發布-訂閱(Publish/Subscrible)模式
二、觀察者模式
定義:觀察者模式定義了一種一對多的依賴關系,讓多個觀察者對象同時監聽某一個主題對象。這個主題對象狀態發生變化時,會通知所有觀察者對象,使它們能夠自行更新自己
下面是觀察者模式結構圖:
該圖示出自“大話設計模式”
下面通過大家都熟悉的生活場景來幫助我們一步步了解觀察者模式
場景:上自習課的時候,困了想睡覺,通常是會跟同桌說:“老師來了叫我下,我睡會”。(大多數人都是這樣吧,嘿嘿)
下面是代碼demo:


//主題類class ConcreteSubject{private IList<ConcreteObserver> lstConcreteObserver = new List<ConcreteObserver>();private string action;//添加觀察者public void Add(ConcreteObserver concreteObserver){lstConcreteObserver.Add(concreteObserver);}//移除觀察者public void Remove(ConcreteObserver concreteObserver){lstConcreteObserver.Remove(concreteObserver);}//通知觀察者類public void Notify(){foreach (ConcreteObserver observer in lstConcreteObserver){observer.Update();}}//定義主題發現的某一狀態public string ConcreteAction{get { return action; }set { action = value; }}}//觀察者類class ConcreteObserver{protected ConcreteSubject subject;protected string name;public ConcreteObserver(ConcreteSubject subject, string name){this.subject = subject;this.name = name;}//觀察者更新行為public void Update(){Console.WriteLine($"{subject.ConcreteAction},{name},別睡覺了,快醒醒!");}}static void Main(string[] args){ConcreteSubject subject = new ConcreteSubject();ConcreteObserver observer = new ConcreteObserver(subject, "michael");subject.Add(observer);subject.ConcreteAction = "老師來了";subject.Notify();Console.Read();}
分析:乍一看是寫的不錯,實現了老師來時同桌通知michael,但是仔細想一下,假如現在情況變了,同桌另一邊的同學在打游戲,也讓老師來時通知一下,怎么辦?這時我們就需要去修改ConcreteSubject類中對觀察者ConcreteObserver的引用,還有另外一種情況,假如某天同桌請假了,通常會讓前后排的同學通知觀察者,即修改觀察者類ConcreteObserver中對ConcreteSubject的引用。這種相互耦合的設計顯然是不太好,當場景變了的時候,不得不去修改原有代碼,違背了開放-封閉原則。
下面看一下大話設計模式中的例子是如何介紹觀察者模式的:


//抽象觀察者類abstract class Observer{public abstract void Update();}//抽象主題類abstract class Subject{private IList<Observer> lstConcreteObserver = new List<Observer>();public void Add(Observer concreteObserver){lstConcreteObserver.Add(concreteObserver);}public void Remove(Observer concreteObserver){lstConcreteObserver.Remove(concreteObserver);}public void Notify(){foreach (Observer observer in lstConcreteObserver){observer.Update();}}}//具體觀察者類class ConcreteObserver:Observer{protected ConcreteSubject subject;protected string name;private string observerState;public ConcreteObserver(ConcreteSubject subject, string name){this.subject = subject;this.name = name;}public override void Update(){this.observerState = subject.ConcreteAction;Console.WriteLine($"the observer's of {name} state is {observerState}");}}//具體主題對象class ConcreteSubject:Subject{ private string action; public string ConcreteAction{get { return action; }set { action = value; }}}static void Main(string[] args){ConcreteSubject subject = new ConcreteSubject();subject.Add(new ConcreteObserver(subject, "michael"));subject.Add(new ConcreteObserver(subject, "jarle"));subject.Add(new ConcreteObserver(subject, "cumming"));subject.ConcreteAction = "Ready";subject.Notify();Console.Read();}
?分析:這次是不是可擴展性更高了?嗯,是的。觀察者與主題對象都依賴于抽象,而不依賴與具體,使得各自變化都不會影響到另一邊
其實現在已經很好了,但是這樣具體的觀察者都要依賴于觀察者的抽象,那能不能再進行優化一次呢?答案是完全可以的。
下面我們用委托事件來解決開頭那個考爛了的面試題,解釋如何優化的
委托:是一種引用類型,表示對具有特定參數列表和返回類型的方法的引用


class Mao{private string name;public delegate void MaoDelegateHandler();public event MaoDelegateHandler MaoEventHandler;public Mao(string name){this.name = name;}public void Miao(){Console.WriteLine($"{this.name}叫了一聲");Notify();}public void Notify(){if (MaoEventHandler != null)MaoEventHandler();}public string Name{get { return name; }set { name = value; }}}class Laoshu{public void Run(){Console.WriteLine("老鼠逃跑了");}}class People{public void Wake(){Console.WriteLine("主人被驚醒了");}}class Program{static void Main(string[] args){Mao mao = new Mao("大臉貓");Laoshu laoshu = new Laoshu();People people = new People();//注冊事件 這兩種方式都可以mao.MaoEventHandler += new Mao.MaoDelegateHandler(laoshu.Run);mao.MaoEventHandler += people.Wake;//貓叫了一聲 會自動調用注冊過的方法 mao.Miao();Console.Read();}}
分析:用委托事件來實現,發布者和訂閱者之間沒有耦合,是不是有優化了一步呢?O(∩_∩)O~
優點:
1.觀察者模式解除了發布者和訂閱者的耦合,兩者都依賴于抽象,而不是具體的,使得兩者可以各自獨立的變化
缺點:
1.觀察者對象如果很多的話,被觀察者通知會耗時增多
2.在被觀察者之間如果有相互依賴的話,會相互調用,導致系統崩潰(小白注意)
適用場景:
1.當一個對象改變需要改變其它對象時,而且不知道有多少個對象需要改變
2.當一個抽象模型有兩個方面,一個方面依賴于另一個方面,將兩者封裝在獨立的對象中以使它們可以各自獨立的變化和復用
?
介紹到這里其實觀察這模式已經結束了,但是我覺得很多小白搞不懂winform開發程序中的grid單擊、雙擊等事件,方法后面的參數sender和e分別是什么。。。下面再簡要分析一下


class Mao{private string name;//聲明委托public delegate void MaoDelegateHandler(object sender, MaoEventArgs e);//聲明事件public event MaoDelegateHandler MaoEventHandler;public class MaoEventArgs : EventArgs{private string name;public MaoEventArgs(string name){this.name = name;}}public Mao(string name){this.name = name;}//發布者 某一行為后 發出通知public void Miao(){Console.WriteLine($"{this.name}叫了一聲");//建立MaoEventArgs對象MaoEventArgs e = new MaoEventArgs(this.Name);//調用通知方法 Notify(e);}public void Notify(MaoEventArgs e){//如果有訂閱者注冊if (MaoEventHandler != null)//執行所有注冊的方法MaoEventHandler(this,e);}public string Name{get { return name; }set { name = value; }}}class Laoshu{//是不是很熟悉,請自行腦補winfom中grid的事件及sender和e是什么public void Run(object sender,Mao.MaoEventArgs e){Mao mao = (sender as Mao);Console.WriteLine($"{mao.Name}來了,老鼠逃跑了");}}class People{public void Wake(object sender, Mao.MaoEventArgs e){Mao mao = (sender as Mao);Console.WriteLine($"{mao.Name}的主人被驚醒了");}}class Program{static void Main(string[] args){Mao mao = new Mao("大臉貓");Laoshu laoshu = new Laoshu();People people = new People();//注冊事件 這兩種方式都可以mao.MaoEventHandler += new Mao.MaoDelegateHandler(laoshu.Run);mao.MaoEventHandler += people.Wake;//貓叫了一聲 會自動調用注冊過的方法 mao.Miao();Console.Read();}}
分析:ok,這下面試的時候再也不怕面試官問你觀察者模式相關知識了吧。。。
?
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接。