熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> .NET編程 >> 正文

.net設計模式之觀察者模式

2022-06-13   來源: .NET編程 


  故事

    小雪是一個非常漂亮的女孩漂亮的女孩總是有很多的追求者而且追求者的隊伍在不斷的變動隨時有人進入這個隊伍也有人退出男孩們追求女孩時總是表現出%的關心當小雪私自游玩時總是不斷收到追求者詢問小雪位置變動的消息小雪也不勝其煩但小雪是如此的一個善良的女孩她總是打斷自己正常的生活回復男孩們的消息而男孩們由於要不斷的關心小雪的位置變化也弄的精疲力竭而且還影響正常的工作

  在這樣一個簡單的故事場景中我們發現了什麼?來看看小雪和男孩們的煩惱

  男孩們必須不斷的詢問小雪的位置變化從而打斷正常的工作

  小雪也要不斷的接受男孩們的詢問有的時候小雪的位置並沒有發生變化還是要不斷的回復男孩們的詢問也影響正常的工作

  如果給各個男孩們回復問題的方式都不盡相同小雪還要知道不同的回復方式而且不斷的有新的男孩們增加進來還不知道未來有什麼新的回復方式

  看到這麼多煩惱我們創意無限的Nokia公司給小雪和男孩們提出了解決方案

    Nokia公司榮譽出品了一款帶有GPRS功能的手機該手機保存著一個訂閱位置變化短信通知的電話列表當該手機檢測到位置發生變化就會向這個訂閱列表裡的所有手機發送短信看到Nokia這個解決方案男孩們和小雪都應該松一口氣他們各自都可以按照自己正常的生活習慣只有狀態發生變化時候各自才會進行通信

  觀察者模式的解決方案

    在上面Nokia的解決方案中就透露出觀察者模式的思想觀察者模式定義了對象之間一對多的依賴當這個對象的狀態發生改變的時候多個對象會接受到通知有機會做出反饋在運行的時刻可以動態的添加和刪除觀察者

  帶著這個定義我們來看看嘗試實現上面的觀察者模式


  首先在觀察者模式中我們必須定義一個所有觀察者都必須實現的接口這樣被觀察者向觀察者發送消息的時候就可以使用統一的方式這也符合面相對象原則中的面向接口編程

  //所有觀察者都必須實現
public interface IBoy
{
//向男孩們顯示小雪位置情況也就是向觀察者發送消息觀察者還可以對此做出反饋
void Show(string address);
}
using System;
//男孩A一個觀察者
public class BoyA : IBoy
{
public void Show(string address)
{
//假設經過處理後為韓文的地址
ConsoleWriteLine(A:+address);
}
}
using System;
//男孩B又一個觀察者
public class BoyB : IBoy
{
public void Show(string address)
{
//假設經過處理後為英語的地址
ConsoleWriteLine(B:+address);
}
}

  下面看看小雪的實現也就是被觀察者主要看看那個訂閱的電話列表和怎樣將消息通知給觀察者

  using System;
using SystemCollectionsGeneric;
public class GPRSMobile
{
//保存一個觀察者列表
private List<IBoy> boys = null;
private string address = ;
public GPRSMobile()
{
boys = new List<IBoy>();
}
//添加觀察者
public void AddBoy(IBoy b)
{
boysAdd(b);
}
public void RemoveBoy(IBoy b)
{
boysRemove(b);
}
//通知
private void Notify(string address)
{
for (int i = ; i < boysCount; i++)
{
boys[i]Show(address);
}
}
//位置發生變化
public void OnAddressChanaged(string newAddress)
{
//假設這裡的地址是中文形式的
Notify(newAddress);
}
}



    看到上面的代碼例子我們可以給觀察者模式的實現總結出這樣幾條規律第一被觀察者必須保存著一個觀察者列表第二所有的觀察者必須實現一個統一的接口

  那觀察者模式到底有哪些好處呢?在上面的例子中我們可以看到被觀察者僅僅依賴於一個實現了觀察者接口的列表我們可以隨時的向這個列表添加觀察者而被觀察者無須關注觀察者是如何實現的當我們向觀察者族中添加一個新的觀察者被觀察者無須作任何改變新的類型只要實現了觀察者接口即可

  在上面的描述中我們仿佛看到了面向接口編程無窮的力量面向接口編程使實現依賴於接口也就是實現依賴於抽象這樣在被依賴對象發生改變的時候只要接口沒有發生變化時依賴對象無須作任何變動

    在現實中存在著很多觀察者模式的實例比如在這個全民炒股的時代每個持有股票的人總是不斷的關注著自己所買的股票的走勢有人天天呆在交易大廳裡看著屏幕上股票價格的走勢有人在工作時間盯著電腦裡股票軟件為此很多公司采取各種各樣的政策來制止這種行為這樣不僅影響正常的上班且股票交易大廳常常人滿為患如果有這樣一個服務只要你訂閱一個短信這個服務就會在你所關注的股票價格發生變動的時候短信通知你這樣你就可以按照正常的順序來做你的工作

  中的觀察者模式

  中微軟給我們帶來一個更好的觀察者模式的實現事件-委托

  在Gof的觀察者模式中(姑且稱之為經典設計模式吧)觀察者必須實現一個統一的接口裡這個接口由委托的簽名來保證了裡的委托就是一個安全的函數指針(之所以說安全是與以前的C指針相比的C的函數指針並不包括函數的簽名比如參數等東西所以可以傳遞一個並不是你期望的函數進去導致運行時出錯由於這種錯誤在運行時發生很難檢查出來)Ok現在以一的委托-事件的例子結束今天的觀察者模式吧


    描述這是一個控制台程序程序接收一個之間整型的輸入程序接收到輸入後開始一個從的循環當循環到你輸入的數字的時候做一些處理我們將以兩種方式來描述這個實例先用常規的方式然後采用委托-事件的方式

  public class Program
{
static void Main(string[] args)
{
ConsoleWriteLine(Please Input a Number:);
int input = ConsoleRead();
if (input < || input > )
{
ConsoleWriteLine(Error);
}
for (int i = ; i <= ; i++)
{
if (i == input)
{
//屏幕輸出
ConsoleWriteLine(iToString());
//彈出提示框
MessageBoxShow(iToString());
//可能還有其他處理
}
}
}
}

  看到這個例子有什麼感覺?

  循環的代碼和處理的代碼混在一起可能還有未知的處理方式添加進來耦合度非常高再看的處理方式吧

  namespace Observer
{
//定義一個委托這裡定義了觀察者方法的簽名就是一個協議吧
public delegate void NumberEventHandler(object senderNumberEventArgs e);
//要傳遞哪些參數到觀察者?在這裡定義注意要繼承自EventArgs
public class NumberEventArgs : EventArgs
{
public NumberEventArgs(int number)
{
_number = number;
}
private int _number;
public int Number
{
get { return _number;}
set { _number = value;}
}
}
//觀察者模式中的主題
public class Subject
{
//定義一個事件就是委托的實例了
public event NumberEventHandler NumberReached;
public void DoWithLoop(int number)
{
for (int i = ; i <= ; i++)
{
//觸發事件的條件到了
if (i == number)
{
NumberEventArgs e = new NumberEventArgs(i);
OnNumberReached(e);
}
}
}
//注意這個方法定義為保護的虛擬的代表子類還可以進行覆蓋改變觸發事件的行為
//甚至可以不觸發事件
protected virtual void OnNumberReached(NumberEventArgs e)
{
//判斷事件是否為null也就是是否綁定了方法
if (NumberReached != null)
NumberReached(this e);
}
}
  
public class MainProgram
{
public static void Main()
{
ConsoleWriteLine(Please Input a Number:);
int input = ConsoleRead();
if (input < || input > )
{
ConsoleWriteLine(Error);
}
Subject s = new Subject();
//給事件綁定方法靜態的
sNumberReached += new NumberEventHandler(msgbox_NumberReached);
MainProgram mp = new MainProgram();
//給事件綁定方法實例方法
sNumberReached += new NumberEventHandler(nsole_NumberReached);
sDoWithLoop(input);
ConsoleRead();
}
void console_NumberReached(object sender NumberEventArgs e)
{
ConsoleWriteLine(eNumberToString());
}
static void msgbox_NumberReached(object sender NumberEventArgs e)
{
MessageBoxShow(eNumberToString());
}
}
}

  雖然這個例子代碼多多了但是是值得的事件觸發的地方和處理的地方完全分離了循環的位置不再需要知道有多少個方法正等著處理它

  總結

    經過幾篇設計模式文章的介紹也許有人會覺得設計模式一直在嘗試解決幾個問題解藕封裝變化設計模式一直在為可維護性可擴展性靈活性努力著所以學習設計模式並不是了解設計模式的原型重要的是了解設計模式的場景和目的這樣你也可以在你自己的工作中總結出自己的設計模式

  有人說中國的數學教育是個錯誤學習數學並不是學習那些定理公式學習那些東西永遠是跟在別人的後面學習數學應該注重數學史的學習循著數學發展的歷史了解前人是怎樣分析問題解決問題學習前人的並不僅僅是為了得到

   

  本來上面的文章已經寫定了但今天看一MVP的文章又有點新的感觸覺得上面的總結又有點偏頗學習模式重要的是她的精髓但是作為初學者即使知道所有設計原則但是卻不知道如何在項目應用是的也許學習設計模式也要從量變引起質變大量的應用先不管是否是過度設計到一定的時候也許就會得到思想上的升華


From:http://tw.wingwit.com/Article/program/net/201311/11302.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.