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

開發設計模式——asp.net中實現觀察者模式

2013-11-13 10:20:51  來源: .NET編程 
    在中實現觀察者模式?難道中的觀察者模式有什麼特別麼?嗯基於Http協議的Application難免有些健忘我是這樣實現的不知道有沒有更好的辦法?

  先談談需求吧以免陷入空談

  最近一個Case 這樣的需求很多客戶端不斷的向Web Application提交數據管理員進入Web的管理頁面可以即時的看到這些數據有多個管理員可以同時浏覽且管理員浏覽的數據從管理員開始監視那個時刻起不能顯示以前的數據從這個場景一看明顯的觀察者模式管理員開始監視時訂閱數據數據到達的時候向所有訂閱了數據的管理員廣播數據

  需求如下圖

  

  

  有了發布者還需要訂閱者我們實現管理員類來訂閱數據

    public class Admin
   {
     /**//// <summary>
     /// 用這個保存所有收到的數據
     /// </summary>
     public IList<string> MessageList
     { get; set; }
     public Admin(Monitor monitor)
     {
         MessageList = new List<string>();
         monitorDataIn += new EventHandler< DataEventArgs>(ReciveMessage);
     }
[img]_//gif[/img]
     private void ReciveMessage(object sender DataEventArgs e)
[img]_//gif[/img]        
MessageListAdd(eMessage);
[img]_//gif[/img]
     }
[img]_//gif[/img]
   }
[img]_//gif[/img]

  Ok需要具備的元素我們都寫好了但是如何讓它們工作起來?如果使Winform程序那將毫無懸念

  分析我們碰到的問題

  第一個問題當客戶端發送一個數據包我們是實例化一個新的Monitor麼?如果是哪麼每次實例化一個全新的Monitor所有在它上面訂閱的事件將全部消失了如果不是那這個Monitor將如何存在呢?總不能真空吧兩個http請求之間如何保存數據呢?不過再把需求一讀好像整個應用程序中就只需要也只能有一個這樣的Monitor呢該是單件模式上場的時候了

  在上面的Monitor的實現中添加下面的代碼

    [img]_//gif[/img]
private static Monitor _instance = null;
[img]_//gif[/img]public static
Monitor Current
[img]_//gif[/img]
   get
[img]_//gif[/img]  
[img]_//gif[/img]
     if (_instance == null)
[img]_//gif[/img]
       _instance = new Monitor();
[img]_//gif[/img]
     return _instance;
[img]_//gif[/img]
   }
[img]_//gif[/img]}

  但是本系統存在多個客戶端所以為了避免多線程造成問題還是來Double Check一下吧修改上面的代碼如下

    [img]_//gif[/img]
public static Monitor Current
[img]_//gif[/img]    
[img]_//gif[/img]
         get
[img]_//gif[/img]
         [img]_//gif[/img]          
object o = new object();
[img]_//gif[/img]
           if (_instance == null)
[img]_//gif[/img]
           [img]_//gif[/img]            
lock (o)
[img]_//gif[/img]
             [img]_//gif[/img]                
if (_instance == null)
[img]_//gif[/img]
                   _instance = new Monitor();
[img]_//gif[/img]
             }
[img]_//gif[/img]
           }
[img]_//gif[/img]          
return _instance;
[img]_//gif[/img]
         }
[img]_//gif[/img]
     }
[img]_//gif[/img]

  (PS:為什麼使用單件就可以跨請求保存實例了呢?因為這裡使用了一個static member保存Monitor的引用static 的GC裡面是被作為Root的詳細內容請參見框架程序設計那本書)

  第二個問題 當管理員頁面的ajax請求的時候每兩個請求如何保存數據?呵呵上面那個問題不是說了麼用單件但是單件是全局存在的我們的管理員是多個每個管理員可以決定是否訂閱數據以及什麼時候訂閱想起來沒?除了全局數據外我們還有Session

  在管理頁面上我放置一個開始監視的按鈕這個按鈕使用ajax請求服務器端的一個HttpHandler在Handler的ProcessRequest方法裡這樣來做

    [img]_//gif[/img]
Admin admin = contextSession[monitor_listener] as Admin;
[img]_//gif[/img]
if(admin == null)
[img]_//gif[/img]
   admin = new Admin(MonitorCurrent);
[img]_//gif[/img]  
contextSession[monitor_listener] = admin;
[img]_//gif[/img]}
[img]_//gif[/img]

  注意由於這個Handler需要訪問Session所以你需要讓這個Handler繼承IRequiresSessionState接口(為什麼使用繼承而不用實現這個術語?實際上這個接口是一個標記接口沒有任何需要實現的成員只是標記這個Handler可以訪問Session我不知道為什麼MS不使用Attribute是不是更合理些)

  在管理頁面還有個一個SetInterval不斷的調用一個含有ajax的方法去請求另外一個Handler這個Handler將Admin收到的數據返回到web頁面讓我們來看看這個Handler的部分實現

    [img]_//gif[/img]
public void ProcessRequest(HttpContext context)
[img]_//gif[/img]    
contextResponseBuffer = true;
[img]_//gif[/img]    
contextResponseExpiresAbsolute = SystemDateTimeNowAddSeconds();
[img]_//gif[/img]  
contextResponseExpires = ;
[img]_//gif[/img]  
contextResponseCacheControl = nocache;
[img]_//gif[/img]
   Admin admin = contextSession[monitor_listener] as Admin;
[img]_//gif[/img]
   if (admin == null || adminMessageCollection == null ||
adminMessageCollectionCount <= )
[img]_//gif[/img]
return;
[img]_//gif[/img]
   string[] messages = new string[adminMessageCollectionCount];
[img]_//gif[/img]  
adminMessageCollectionCopyTo(messages );
[img]_//gif[/img]
   StringBuilder sb = new StringBuilder();
[img]_//gif[/img]
   for (int i = ; i < messagesLength; i++)
[img]_//gif[/img]  
[img]_//gif[/img]    
sbAppendFormat(<li>{}</li> messages);
[img]_//gif[/img]
   }
[img]_//gif[/img]  
adminMessageCollectionClear();
[img]_//gif[/img]  
contextSession[monitor_listener] = admin;
[img]_//gif[/img]  
contextResponseWrite(sb);
[img]_//gif[/img]  
contextResponseFlush();
[img]_//gif[/img]}
[img]_//gif[/img]

  OK一個在環境中實現的觀察者模式基本上就算完成了不過上面只有怎樣訂閱那什麼時候取消訂閱了可以在Session_End事件裡面取消訂閱

  還查看了一些關於長連接的文章發現這個不錯准備改進一下

  完整的代碼稍後提供希望這塊轉頭能引來一些玉

  寫完這個Post後本來想把完整代碼實現傳上來後來看到不少園友提出異議看了大家的留言後我也一直在思索我為什麼這樣做?當初我是怎樣想到這個解決方案的?我在幾個解決方案之間做了取捨了麼?我這樣做是不是矯枉過正了?經過這些思考有了現在的這個Post

  首先我進一步談一下需求

  這是一個Web Application有很多客戶端向服務器端提交數據(客戶端是C++的以httppost方式向服務器端提交二進制數據服務器端解析這個二進制包數據提交很頻繁)管理員可以進入監視頁面浏覽這些數據數據要即時的客戶端發來一條管理員屏幕上要馬上可以看到允許多個管理員同時監視即時數據所有管理員看到的數據都是一樣的(目前是這樣的也許以後對管理員要分角色各角色管理員看到的信息將不同)

  由於數據提交非常頻繁客戶要求不允許頻繁的數據庫操作所以我將數據保存在一個IList的緩存裡面當這個IList的大小超過了我在配置文件裡定義的大小的時候就將數據批量插入到數據庫

  下面我將以我當初思考的思路為主線描述

  第一個版本

    //在程序裡我寫了一個靜態類這個靜態類保存整個程序中共享的一些數據
相當於原來的//Application對象但是靜態成員是編譯期類型檢查的
public static ApplicationData
{
  //這個隊列用來保存客戶端傳遞過來的數據當隊列達到一定長度的時候同步到數據庫
  public static Queue<DataHead> OperateDataList = new Queue<DataHead>();
//這個List也是保存客戶端傳遞過來的數據的但它是為監視准備數據的
//當一個監視頁面的請求到來的時候將這個List的數據Response過去然後Clear這個//List
  public static IList<DataHead> MonitorDataList = new List<DataHead>();
}
public class ReciveDataHandler : IHttpHandler
{
  //……
  Public void ProcessRequest(HttpContext context)
  {
    //解析從客戶端傳遞過來的數據
    DataHead data = GetData(context);
    OperateDataListAdd(data);
    If(OperateDataListCount > BufferSize)
    {
        //將數據寫入到數據庫
        AddToBase();
}
    MonitorDataListAdd(data);
}
}
//監視頁面從這裡獲取數據
public class MonitorHandler : IHttpHandler
{
  //……
  Public void ProcessRequest(HttpContext context)
  {
    If(MonitorDataListCount > )
    {
        //將MonitorDataList裡的數據Response出去
        OutPut();
        MonitorDataListClear();
}
}
}

  說實話我當初做出這個的時候覺得一點問題都沒有開始的時候客戶測試也沒有發現任何問題終於有一天客戶和我同時測試部署在同一IIS的時候問題出現了只有一個監視頁面有數據看到這個後我還百思不得其解順著程序的執行流程一步一步走下去沒有找出任何錯誤後來做了下日志原來MonitorDataList是一個全局共享的一個在監視把數據Clear了後別人就無法獲取數據了不知道有沒有人這樣做過有時候忘記了自己正在做一個web程序而web程序是一個並發的對一些共享資源的訪問有著微妙的問題如果沒有記住這點按照程序流程的執行步驟是找不出任何問題的

  怎麼辦?再一看這不是事件訂閱所描述的場景麼?所以就有了上一篇Post的Solution不過那個方案受到不少人質疑其中金色海洋提出這樣的方法

    Public class ReciveData : IHttpHandler
{
  //………
  //將客戶端傳遞過來的數據存入數據庫
}

Public class MonitorHandler : IHttpHandler
{
  //………
  //為null的時候說明該管理員第一次監視
  If(Session[id] == null)
  {
    //根據時間從服務器取出數據
    //並將取出數據的最後一個id保存在session中
    Session[id] = id;
}
//不為null則說明該管理員已經開始監視了
Else
{
  //根據session裡保存的最後一個id取出大於那個id的數據
  Session[id] = currentId;
}
}

  看似這個方案不錯我嘗試著將我的程序修改為這樣但是我將上面的代碼編寫完我發現我不可以再進行下去了上面的方案滿足不了我的需求客戶明確要求了客戶端提交的數據要先緩存然後緩存超過配置大小(這個大小還需要可以在配置文件裡面配置以便可以經過測試找出一個最合理的值)而這種Session記錄的方案是依靠數據庫來保存數據這個Session[id]就相當於一個游標這個游標指向的是數據庫那好我們將Session[id]指向緩存數據但是請注意緩存隨時可能超過設置大小而被同步到數據庫並被清空

  經過一番思考後我還是回到我自己的Solution上不過我又有了新的看法了不是要將數據先緩存麼?看看這個緩存實際上她也是個觀察者至於她執行怎樣的緩存策略是她的事情如是我又有了一個新類

    [img]_//gif[/img]
public class DataEventArgs : EventArgs
[img]_//gif[/img]
         public string Message
[img]_//gif[/img]
         [img][/img]{get;set;}
[img]_//gif[/img]
         public DataEventArgs(string message)
[img]_//gif[/img]
         [img]_//gif[/img]          
thisMessage = message;
[img]_//gif[/img]
         }
[img]_//gif[/img]
     }
[img]_//gif[/img]
public class Monitor
[img]_//gif[/img]
[img][/img]{
[img]_//gif[/img]
     public event EventHandler<DataEventArgs> DataIn;
[img]_//gif[/img]
     private void SendData(string message)

[img]_//gif[/img]    
[img]_//gif[/img]
         if (DataIn != null)
[img]_//gif[/img]
         [img]_//gif[/img]          
DataEventArgs e = new DataEventArgs(message);
[img]_//gif[/img]
           DataIn(this e);
[img]_//gif[/img]
         }
[img]_//gif[/img]
     }
[img]_//gif[/img]
     /**//// <summary>
[img]_//gif[/img]
     /// 這個方法被一個HttpHandler調用客戶端向這個Handler發送數據
[img]_//gif[/img]
     /// 數據處理後作為字符串傳遞給該方法該方法然後將數據廣播出去
[img]_//gif[/img]
     /// </summary>
[img]_//gif[/img]
     /// <param name=message>處理後的數據</param>
[img]_//gif[/img]
     public void ReciveData(string message)
[img]_//gif[/img]    
[img]_//gif[/img]
         SendData(message);
[img]_//gif[/img]
     }
[img]_//gif[/img]}
[img]_//gif[/img]


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