1. 程式人生 > >面試被問設計模式?不要怕看這裡:觀察者模式

面試被問設計模式?不要怕看這裡:觀察者模式

本系列:

上一篇中,我給大家詳細講解了一下工廠模式以及面試中可能會被問到的關鍵點,我們先來溫習溫習。

工廠模式的關鍵點:

一、工廠模式分為簡單工廠,工廠和抽象工廠

二、三種工廠的實現是越來越複雜的

三、簡單工廠通過構造時傳入的標識來生產產品,不同產品都在同一個工廠中生產,這種判斷會隨著產品的增加而增加,給擴充套件和維護帶來麻煩

四、工廠模式無法解決產品族和產品等級結構的問題

五、抽象工廠模式中,一個工廠生產多個產品,它們是一個產品族,不同的產品族的產品派生於不同的抽象產品(或產品介面)。

另外,如果面試官問你在專案中有沒有用過,千萬千萬不要再舉連線資料庫的例子了,真的。。。

好了,溫習完了以後,我們下面來講講今天的內容,觀察者模式。

單例,工廠,觀察者,我認為是設計模式三傑,為何這麼說,因為這三種設計模式使用頻率最高,變化最多,而且覆蓋面最廣,當然,也是面試中最容易被問到的。當然,像裝飾,建造,策略這幾個也是經常會用到的,後面文章中我會一一展開討論。

從這裡開始,我們在介紹設計模式之前,需要強調一下角色。實際上從上篇工廠模式中,就已經有角色的概念。大家想想,工廠模式中有哪些角色?沒錯,就是工廠角色和產品角色。同樣,在觀察者模式中,也有兩個角色,就是觀察者和被觀察者。在理解設計模式的時候,首先要有個概念,就是每個角色都對應這一個類,比如觀察者模式,觀察者肯定對應著一個觀察者類,被觀察者肯定對應的被觀察者類。那麼設計模式實際上就是通過面向物件的特性,將這些角色解耦。

觀察者模式本質上就是一種訂閱/釋出的模型,從邏輯上來說就是一對多的依賴關係。什麼意思呢?好比是一群守衛盯著一個囚犯,只要囚犯一有異動,守衛就必須馬上採取行動(也有可能是更新狀態,本質上也是一種行動),那麼守衛就是觀察者,囚犯就是被觀察者。

在一個系統中,實現這種一對多的而且之間有一定關聯的邏輯的時候,由於需要保持他們之間的協同關係,所以最簡便的方法是採用緊耦合,把這些物件繫結到一起。但是這樣一來,一旦有擴充套件或者修改的時候,開發人員所面對的難度非常大,而且很容易造成Bug。那麼觀察者模式就解決了這麼一個問題,在保持一系列觀察者和被觀察者物件協同工作的同時,把之間解耦了。

好了,廢話不多,擼程式碼:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107 //被觀察者publicinterfaceIObject{IList<IMonitor>ListMonitor{get;set;}//定義觀察者集合,因為多個觀察者觀察一個物件,所以這裡用集合stringSubjectState{get;set;}//被觀察者的狀態voidAddMonitor(IMonitor monitor);//新增一個觀察者voidRemoveMonitor(IMonitor monitor);//移除一個觀察者voidSendMessage();//向所有觀察者傳送訊息}publicclassSubject:IObject{privateIList<IMonitor>listMonitor=newList<IMonitor>();publicstringSubjectState//被觀察者的狀態{get;set;}publicIList<IMonitor>ListMonitor//實現具體的觀察者列表屬性{get{returnlistMonitor;}set{listMonitor=value;}}publicvoidAddMonitor(IMonitor monitor)//實現具體的新增觀察者方法{listMonitor.Add(monitor);}publicvoidRemoveMonitor(IMonitor monitor)//實現具體的移除觀察者方法{listMonitor.Remove(monitor);}publicvoidSendMessage()//實現具體的傳送訊息方法{foreach(IMonitorminlistMonitor)//傳送給所有新增過的觀察者,讓觀察者執行update方法以同步更新自身狀態{m.Update();}}}//觀察者publicinterfaceIMonitor//定義觀察者介面{voidUpdate();}publicclassMonitor:IMonitor//實現具體觀察者{privatestringmonitorState="Stop!";//觀察者初始狀態,會隨著被觀察者變化而變化privatestringname;//觀察者名稱,用於標記不同觀察者privateIObject subject;//被觀察者物件publicMonitor(IObject subject,stringname)//在構造觀察者時,傳入被觀察者物件,以及標識該觀察者名稱{this.subject=subject;this.name=name;Console.WriteLine("我是觀察者{0},我的初始狀態是{1}",name,monitorState);}publicvoidUpdate()//當被觀察者狀態改變,觀察者需要隨之改變{monitorState=subject.SubjectState;Console.WriteLine("我是觀察者{0},我的狀態是{1}",name,monitorState);}}//前端呼叫staticvoidMain(string[]args){IObject subject=newSubject();subject.AddMonitor(newMonitor(subject,"Monitor_1"));subject.AddMonitor(newMonitor(subject,"Monitor_2"));subject.AddMonitor(newMonitor(subject,"Monitor_3"));subject.SubjectState="Start!";subject.SendMessage();Console.Read();}}

結果如下

123456 我是觀察者Monitor_1,我的初始狀態是Stop!我是觀察者Monitor_2,我的初始狀態是Stop!我是觀察者Monitor_3,我的初始狀態是Stop!我是觀察者Monitor_1,我的狀態是Start!我是觀察者Monitor_2,我的狀態是Start!我是觀察者Monitor_3,我的狀態是Start!

這樣就完成了一個觀察者模式。我們回過頭來看看,在被觀察者中,我定義了一個集合用來存放觀察者,並且我寫了一個Add方法一個Remove方法來新增和移除觀察者,這體現了一對多的關係,也提供了可以控制觀察者的方式。所以,我們得到第一個關鍵點:每個觀察者需要被儲存到被觀察者的集合中,並且被觀察者提供新增和刪除的方式。

然後我麼再看一下,觀察者和被觀察者之間的互動活動。不難發現,我是在新增一個觀察者的時候,把被觀察者物件以建構函式的形式給傳入了觀察者。最後我讓被觀察者執行sendmessage方法,這時會觸法所有觀察著的update方法以更新狀態。所以我們得到第二個關鍵點,被觀察者把自己傳給觀察者,當狀態改變後,通過遍歷或迴圈的方式逐個通知列表中的觀察者。

好了,到這裡你應該可以把握住觀察者模式的關鍵了。但這裡有個問題,被觀察者是通過建構函式引數的形式,傳給觀察者的,而觀察者物件時被Add到被觀察者的List中。所以,我們得到第三個關鍵點,雖然解耦了觀察者和被觀察者的依賴,讓各自的變化不大影響另一方的變化,但是這種解耦並不是很徹底,沒有完全解除兩者之間的耦合。

有很多同學比較怕被問到觀察者模式,特別是搞.Net的同學。為什麼呢?因為一旦涉及到觀察者模式,必然會涉及到2中型別,委託和事件。因為很多人並不理解委託和事件,或者只停留在潛層次。那麼,委託,事件,和觀察者模式到底有什麼關係呢?

首先我們來看委託。委託說白了,就是可以把方法當做另一個方法引數來傳遞的東東,當然方法簽名需要注意一下。委託可以看做是方法的抽象,也就是方法的“類”,一個委託的例項可以是一個或者多個方法。我們可以通過+=或者-=把方法繫結到委託或者從委託移除。

再來看事件,實際上事件是一種特殊的委託。怎麼說呢,首先事件也是委託,只是在宣告事件的時候,需要加上event,如果你用reflector去看一個事件,你會發現裡面就3樣東西,一個Add_xxxx方法,一個Remove_xxx方法,一個委託。說道這裡,是不是覺得和上面我們定義被觀察者時的Add方法,Remove方法有些聯絡?

沒錯,實際上.Net的事件機制就是觀察者模式的一種體現,並且是利用委託來實現。本質上事件就是一種訂閱-釋出模型也就是觀察者模式,這種機制中包含2個角色,一個是釋出者,一個是訂閱者。釋出者類也就類似於被觀察者,釋出者類包含事件和委託定義,以及其之間的關係,釋出者類的物件呼叫事件通知其他訂閱者。而訂閱者類也就類似於觀察者,觀察者接受事件,並且提供處理的邏輯。

也就是說,訂閱者物件(觀察者)中的方法會繫結到釋出者(被觀察者)物件的委託中,一旦釋出者(被觀察者)中事件被呼叫,釋出者(被觀察者)就會呼叫委託中繫結的訂閱者(觀察者)的處理邏輯或者說是處理程式,這就是通過觀察者模式實現的事件,雖然這段話比較拗口,但是我想理解起來應該還是不太難把。。。

好了,你雖然現在已經對面試官逼逼叨了上面一大通,但是賤賤的面試官仍然想challenge你一下,否則不就是太沒面子麼。。。

  1. 你剛剛說了,在普通的觀察者模式中,解耦並不徹底,那麼在事件的釋出訂閱模型中,解耦徹底嗎?為什麼?

答案是肯定的。因為在事件中,訂閱者和釋出者之間是通過把事件處理程式繫結到委託,並不是把自身傳給對方。所以解決了觀察者模式中不完全解耦的問題。這也是關鍵點之四

2. 通過委託繫結方法來實現觀察者模式,會不會有什麼隱患?

有的,通過+=去把方法繫結到委託,很容易忘記-=。如果只繫結不移除,這個方法會一直被引用。我們知道GC去回收的時候,只會處理沒有被引用的物件,只要是還被引用的物件時不會被回收掉的。所以如果在長期不關閉的系統中(比如監控系統),大量的程式碼使用+=而不-=,執行時間長以後有可能會記憶體溢位。

3. 事件,委託,觀察者模式之間的關係

這個上面已經提到了,委託時一種型別,事件是一種特殊的委託,觀察者模式是一種設計模式,事件的機制是觀察者模式的一種實現,其中訂閱者和釋出者通過委託實現協同工作。

好的,最後我們還是來歸納一下觀察者模式的關鍵點

一、每個觀察者需要被儲存到被觀察者的集合中,並且被觀察者提供新增和刪除的方式。

二、被觀察者把自己傳給觀察者,當狀態改變後,通過遍歷或迴圈的方式逐個通知列表中的觀察者。

三、雖然解耦了觀察者和被觀察者的依賴,讓各自的變化不大影響另一方的變化,但是這種解耦並不是很徹底,沒有完全解除兩者之間的耦合。

四、在事件中,訂閱者和釋出者之間是通過把事件處理程式繫結到委託,並不是把自身傳給對方。所以解決了觀察者模式中不完全解耦的問題

注意點:在使用委託繫結方法時,需要注意移除方法,否則可能會造成記憶體溢位。

建議:不能理解事件和委託的同學,好好地把事件和委託看一看,並且自己寫點程式碼加深印象。

打賞支援我寫出更多好文章,謝謝!

打賞作者

打賞支援我寫出更多好文章,謝謝!

任選一種支付方式