圖解“管道過濾器模式”應用實例:SOD框架的命令執行管道

来源:http://www.cnblogs.com/bluedoctor/archive/2016/03/26/5278995.html
-Advertisement-
Play Games

管道和過濾器 管道和過濾器是八種體繫結構模式之一,這八種體繫結構模式是:層、管道和過濾器、黑板、代理者、模型-視圖-控制器(MVC) 表示-抽象-控制(PAC)、微核、映像。 管道和過濾器適用於需要漸增式處理數據流的領域,而常見的“層”模式它 能夠被分解成子任務組,其中每個子任務組處於一個特定的抽象 ...


管道和過濾器

管道和過濾器是八種體繫結構模式之一,這八種體繫結構模式是:層、管道和過濾器、黑板、代理者、模型-視圖-控制器(MVC) 表示-抽象-控制(PAC)、微核、映像。

管道和過濾器適用於需要漸增式處理數據流的領域,而常見的“層”模式它 能夠被分解成子任務組,其中每個子任務組處於一個特定的抽象層次上。

按照《POSA(面向模式的軟體架構)》里的說法,管道過濾器(Pipe-And-Filter)應該屬於架構模式,因為它通常決定了一個系統的基本架構。管道過濾器和生產流水線類似,在生產流水線上,原材料在流水線上經一道一道的工序,最後形成某種有用的產品。在管道過濾器中,數據經過一個一個的過濾器,最後得到需要的數據。

clip_image001
管道&過濾器模型的基本部件都有一套輸入輸出介面。每個部件從輸入介面中讀取數據,經過處理,將結果數據置於輸出介面中,這樣的部件稱為“過濾器”。這種模型的連接者將一個過濾器的輸出傳送到另一個過濾器的輸入,
我們把這種連接者稱為“管道”。在這種模型中,過濾器必須是獨立的實體,每一個過濾器的狀態不受其它過濾器的影響,並且,雖然人們對過濾器的輸入輸出有一定的規定,但過濾器並不需要知道向它提供數據流的過濾器和
它要提供數據流的過濾器的內部細節。任何兩個過濾器,只要它們之間傳送的數據遵守共同的規約就可以相連接。
每個過濾器都有自己獨立的輸入輸出介面,如果過濾器間傳輸的數據遵守其規約,只要用管道將它們連接就可以正常工作。

查詢的關註點

基於以上管道和過濾器特點,它為處理數據流的系統提供了一種良好的結構,每一個處理步驟封裝在一個過濾器組件中,數據通過相鄰的過濾器之間的管道傳輸。在程式處理中,也有類似的這種數據流,最常見的就是命令處理的數據流,它從最開始的查詢命令,到最後的結果輸出,會經過多個步驟,以ADO.NET來說,執行一個查詢會經過以下過程:

查詢命令:

  • 獲取數據集:
    1. 打開資料庫連接 IDbConnection
    2. 創建命令對象 IDbCommand
    3. 創建數據適配器 IDataAdapter
    4. 填充數據集 IDataAdapter.Fill(DataSet)
    5. 關閉資料庫連接
    6. 返回數據集 DataSet 
  • 獲取數據閱讀器
    1. 打開資料庫連接 IDbConnection
    2. 創建命令對象 IDbCommand
    3. 執行數據閱讀器查詢 IDbCommand.ExecuteReader
    4. 返回數據閱讀器 IDataReader
    5. 關閉資料庫連接

 非查詢命令:

  1. 打開資料庫連接 IDbConnection
  2. 創建命令對象 IDbCommand
  3. 執行查詢 IDbCommand.ExecuteNonQuery()
  4. 關閉資料庫連接 
可以看到,上面這幾種查詢命令的執行,都要經過幾個相同的步驟:打開資料庫連接,創建命令對象,執行查詢,返回結果,關閉資料庫連接,這幾個步驟是有嚴格順序的,前後依賴的,就像水流一般,因此,我們也可以利用“管道--過濾器”模式,在查詢命令的執行過程中,插入某些特定的處理邏輯。 從最終使用者的角度來說,一個查詢有4個關註點:
  • 查詢前
  • 查詢中
  • 查詢後
  • 查詢異常
 其中,查詢中是ADO.NET等數據訪問組件內部的處理過程,一般不能直接提供用戶可以切入和干預的觀察點,那麼剩下3個關註點,就是我們可以用的,這3個關註點,就像一個水管的三個閥門一樣。

SOD框架的命令處理管道

命令處理介面

SOD框架現在也提供了這樣的三個關註點,使得使用組件的用戶,能夠無需修改組件內部的代碼,改變和觀察組件的處理情況,這三個關註點對應的是 ICommandHandle介面的3個方法:  
    /// <summary>
    /// 查詢命令處理器介面
    /// </summary>
    public interface ICommandHandle
    {
        /// <summary>
        /// 獲取當前適用的資料庫類型,如果通用,請設置為 UNKNOWN
        /// </summary>
        DBMSType ApplayDBMSType { get; }
        /// <summary>
        /// 執行前處理,比如預處理SQL,補充設定參數類型,返回是否繼續進行查詢執行
        /// </summary>
        /// <param name="db">資料庫訪問對象</param>
        /// <param name="SQL"></param>
        /// <param name="commandType"></param>
        /// <param name="parameters"></param>
        /// <returns>返回真,以便最終執行查詢,否則將終止查詢</returns>
        bool OnExecuting(CommonDB db, ref string SQL, CommandType commandType, IDataParameter[] parameters);

        /// <summary>
        /// 執行過程中出錯情況處理
        /// </summary>
        /// <param name="cmd"></param>
        /// <param name="errorMessage"></param>
        void OnExecuteError(IDbCommand cmd, string errorMessage);
        /// <summary>
        /// 查詢執行完成後的處理,不管是否執行出錯都會進行的處理
        /// </summary>
        /// <param name="cmd"></param>
        /// <param name="recordAffected">命令執行的受影響記錄行數</param>
        long OnExecuted(IDbCommand cmd, int recordAffected);
    }

 

一圖勝千言,先看下麵的“SOD框架命令處理管道”圖:

 

 由前面介面的定義並結合這個圖,可以看到查詢命令在“數據訪問”這個管道裡面流動過程:

  • 首先,它在 OnExecuting 這個過濾插口位置改變命令的行為特征,比如SQL預處理,終止查詢等,發起非同步操作等;
  • 接著,查詢命令由Ado.Net進行處理,而此時是很有可能發生查詢錯誤的情況的,那麼提供一個OnExecuteError 過濾插口,讓錯誤信息可以被一些過濾器使用,比如查詢操作日誌組件;
  • 最後,不論前面命令執行是否成功,命令執行完了還需要進行一些其它的處理,那麼提供一個OnExecuteError 過濾插口,比如觀察命令執行的結果行/影響行,命令的執行時間,返回非同步通知等。

 根據這裡定義的命令執行管道介面,最典型的實現就是可以用來記錄查詢日誌,比如下麵的 CommandExecuteLogHandle 類:

   /// <summary>
    /// 命令執行日誌處理器,可以記錄SQL和參數,執行時間等信息
    /// </summary>
    public class CommandExecuteLogHandle :ICommandHandle
    {
        /// <summary>
        /// 初始化一個命令執行日誌處理器
        /// </summary>
        public CommandExecuteLogHandle()
        {
            this.CurrCommandLog = new CommandLog(true);
            //這裡需要進行一些初始化檢查,設置日誌路徑等
            if (CommandLog.DataLogFile == null)
                CommandLog.DataLogFile = "~/sql.log";
            CommandLog.SaveCommandLog = true;
        }

        public CommandLog CurrCommandLog { get; private set; }


        public bool OnExecuting(CommonDB db, ref string SQL, CommandType commandType, IDataParameter[] parameters)
        {
            this.CurrCommandLog.ReSet();
           return true;
        }

        public void OnExecuteError(IDbCommand cmd, string errorMessage)
        {
            CurrCommandLog.WriteErrLog(cmd, "AdoHelper:" + errorMessage);
        }

        public long OnExecuted(IDbCommand cmd, int recordAffected)
        {
            long elapsedMilliseconds;
            CurrCommandLog.WriteLog(cmd, "AdoHelper", out elapsedMilliseconds);
            CurrCommandLog.WriteLog("RecordAffected:"+recordAffected , "AdoHelper");
            return elapsedMilliseconds;
        }

        public DBMSType ApplayDBMSType
        {
             get { return DBMSType.UNKNOWN; }
} }

註意,這裡 ApplayDBMSType 返回 UNKNOW,表示當前介面實現類性適合於任意資料庫查詢的情況。

另外,日誌過濾器內部使用了框架內置的 CommandLog 類,它可以非同步的記錄SQL執行情況,並能記錄查詢時間大於某個值的查詢,詳細請看《PDF.NET的SQL日誌》。

 

再看下麵,我們實現一個用於處理Oracle查詢的“過濾器”組件,它會在查詢開始前,對SQL進行一些預處理,比如將本來使用於SQLSERVER的SQL語句格式,處理成Oracle特有的格式:

   /// <summary>
    /// 自定義的Oracle命令處理器,用於處理特殊的欄位名大寫問題
    /// </summary>
    public class OracleCommandHandle : ICommandHandle
    {

        public bool OnExecuting(CommonDB db, ref string sql, System.Data.CommandType commandType, System.Data.IDataParameter[] parameters)
        {
            sql= sql.Replace("[", "").Replace("]", "").Replace("@", ":").ToUpper();
            //設置SQLSERVER相容性為假,避免命令對象真正執行的時候再進行Oracle的查詢語句的預處理。
            db.SqlServerCompatible = false;
            //返回真,以便最終執行查詢,否則將終止查詢
            return true;
        }

        public void OnExecuteError(System.Data.IDbCommand cmd, string errorMessage)
        {
            
        }

        public long OnExecuted(System.Data.IDbCommand cmd, int recordAffected)
        {
            return 1;
        }

        public PWMIS.Common.DBMSType ApplayDBMSType
        {
            get { return PWMIS.Common.DBMSType.Oracle; }
        }
    }

 註意:上面這個實現類,指明瞭當前命令執行過濾器組件,僅使用於Oracle資料庫,當前如果是其它資料庫類型,會忽略該過濾器組件。

除此之外,是不是還可以寫一個過濾器組件,監視下當前查詢是否執行成功,如果成功,將查詢的SQL和參數發送到消息隊列,進行非同步更新其它資料庫?

開閉原則

所以,SOD框架的“命令執行管道”給予了最終用戶在不改變原有數據訪問組件的內部實現的情況下,一個監視和處理命令執行過程的“視窗”,一個或者多個對查詢命令的“過濾器”組件,這正是面向對象原則之一的開閉原則

我們來看下百度百科對開閉原則的解釋

開閉原則(OCP)是面向對象設計中“可復用設計”的基石,是面向對象設計中最重要的原則之一,其它很多的設計原則都是實現開閉原則的一種手段。

遵循開閉原則設計出的模塊具有兩個主要特征:

(1)對於擴展是開放的(Open for extension)。這意味著模塊的行為是可以擴展的。當應用的需求改變時,我們可以對模塊進行擴展,使其具有滿足那些改變的新行為。也就是說,我們可以改變模塊的功能。

(2)對於修改是關閉的(Closed for modification)。對模塊行為進行擴展時,不必改動模塊的源代碼或者二進位代碼。模塊的二進位可執行版本,無論是可鏈接的庫、DLL或者.EXE文件,都無需改動。

 

既然命令執行管道如此有用,我們該如何使用呢?還是直接看示例代碼比較簡單:

   /// <summary>
    /// 用來測試的本地 資料庫上下文類
    /// </summary>
    public class MyOracleDbContext : DbContext  
    {
        public MyOracleDbContext()
            : base("local")
        {
            //local 是連接字元串名字
            //註冊日誌處理器和Oracle命令處理器
            base.CurrentDataBase.RegisterCommandHandle(new CommandExecuteLogHandle());
            base.CurrentDataBase.RegisterCommandHandle(new OracleCommandHandle());
        }

        #region 父類抽象方法的實現

        protected override bool CheckAllTableExists()
        {
            //創建用戶表
            CheckTableExists<User>();
            return true;
        }

        #endregion
    }

在這個 MyOracleDbContext 類中,我們註冊了2個過濾器組件:日誌過濾器和Oracle命令過濾器。

如果當前連接配置名 local 對應的資料庫訪問提供程式不是Oracle了怎麼辦?

不用擔心,前面說過, Oracle命令過濾器僅對Oracle數據訪問有效,其它資料庫訪問會忽略,而日誌過濾器組件它是適用於任何資料庫訪問的。

上面的示例代碼中,CurrentDataBase 對象其實就是 SOD框架的 AdoHelper對象,所以,只要你使用SOD框架,那麼不管你使用的是框架的ORM,SQL-MAP,Data Controls功能,甚至是最簡單的“SqlHelper”類應用,你都可以享受到SOD框架的“命令執行管道”帶給你d便利!

與“觀察者模式”的區別

.NET框架中,對觀察者模式最常見的實現就是“事件”,事件可以實現監視某個對象的改變情況然後發起事件通知,最後由事件處理程式完成處理。在本文描述的查詢處理場景中,也可以在查詢處理前,處理後,發生異常這3個“觀察點”發起事件,並且,事件也可以實現“多播”,一個事件可以由多個事件處理程式來處理。所以,從這個意義上來說,“管道-過濾器”模式跟“觀察者”模式功能上很相似的,但為何SOD框架不選擇後者來實現呢?

我認為,主要區別有以下幾個方面:

在架構層面上,

“管道-過濾器”模式通常用於架構設計層面,是一種“架構模式”,比如分層架構;而觀察者模式一種面向對象編程的模式,運用的領域不一樣。

“管道-過濾器”模式讓架構實現松耦合;而觀察者模式的觀察者和被觀察者之間,往往是緊密耦合的關係。

在具體使用形式上,

“架構模式”可以通過配置文件來提供附件的一種功能實現,比如ASP.NET的HttpHandle,ASP.NET MVC的Controller上的Filter等,所以它的實現是松耦合的;

而觀察者模式往往體現在編寫的代碼中,用事件來處理代碼來實現,所以它往往是緊耦合的。

在業務語義上,

“管道-過濾器”是用於處理流動的載體的,比如數據,信息或其它具有流動特性的物體,方便進行多環節,多層次的攔截或者加工處理,並且每個處理環節都有序的,流動和有序,這是這類業務最重要的特征;

“事件”處理的客體範圍更廣,事件的客體沒有固定的形態,事件的發生和處理可能都是無序的。

 

其它方面的考慮,事件使用前總是需要聲明事件掛鉤,會多增加一些代碼量,並且使用完成之後,往往還需要解除掛鉤,否則可能發生記憶體泄漏,請參見 我另外一篇文章《Release編譯模式下,事件是否會引起記憶體泄漏問題初步研究》。

總結

所以,在當前這個數據查詢的場景中,對於查詢命令的處理,採用“管道-過濾器”模式來實現一個命令執行管道,是最合適的,它讓人在業務語義上更加明確,並且使用上更加靈活,代碼實現量也最小,而且不需要修改原有的代碼實現,符合開閉原則。

到目前為止,我還沒有看到其它 數據處理框架/ORM框架 比較明確的提供了關註和干預組件內部查詢執行過程的功能,都只能進行外部的攔截,如果你有這樣的需求,來試試SOD框架帶給你的靈活和自由吧!

附註:

SOD不僅僅是一個ORM,它還有SQL-MAP和DataControl,具體可以看框架官網 http://www.pwmis.com/sqlmap ,9年曆史鑄就的成果,堅固可靠。

非常感謝你看到這裡,相信你初步瞭解了SOD框架的基本功能,如果您還有其它問題,歡迎你在項目的開源網站 http://pwmis.codeplex.com的討論區發帖,或者去官方博客相關文章回帖也可。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 本文翻譯自《effective modern C++》,由於水平有限,故無法保證翻譯完全正確,歡迎指出錯誤。謝謝! 如果你需要寫一個以名字作為參數,並記錄下當前日期和時間的函數,在函數中還要把名字添加到全局的數據結構中去的話。你可能會想出看起來像這樣的一個函數: std::multiset name ...
  • java的參數傳遞機制和C、C++其實很像,前兩天在改一個網站非同步介面的時候,掉入坑裡,之前是外包寫的代碼,springMVC里起了一個多線程,但是參數傳遞的時候傳的是一個model對象,所以所有線程都共用了這個對象,結果跑出來的result一塌糊塗。 下麵進入正題,先看一段demo代碼吧 java ...
  • 創建型模式抽象了實例化過程。它們幫助一個系統獨立於如何創建、組合和表示它的那些對象。 1.抽象工廠模式(ABSTRACT FACTORY) 意圖 提供一個創建一系列相關或相互依賴對象的介面,而不需指定他們具體的類。 ( 抽象工廠模式可以向客戶端(Client指代碼模式的使用者,後文類同)提供一個介面 ...
  • 翻譯前言:我在理解複雜事件處理(CEP)方面一直有這樣的困惑--為什麼這種計算模式是有效的,能夠分析得到有用的結果?為什麼它會快?我始終還沒有找到我期望的答案。不像map-reduce模型,google的論文非常清楚的描述了它的場景;或者disruptor框架,原作者清晰地解釋了它為什麼會快。在試圖... ...
  • 什麼是ORM框架: ORM即對象關係映射(Object Relational Mapping,簡稱ORM),是一種為瞭解決面向對象與關係資料庫存在的互不匹配的現象的技術。簡單的說,ORM是通過使用描述對象和資料庫之間映射的元數據,將程式中的對象自動持久化到關係資料庫中。 自己也用過很多ORM框架,比 ...
  • Atitit.木馬病毒 webftp 的原理跟個設計 ftp木馬的效果 文件傳播 文件列表 文件內容查看 作者:: ★(attilax)>>> 綽號:老哇的爪子 ( 全名::Attilax Akbar Al Rapanui 阿提拉克斯 阿克巴 阿爾 拉帕努伊 ) 漢字名:艾龍, EMAIL:1466 ...
  • Atitit。Cas機制 軟體開發 編程語言 無鎖機制 java c# php 1. 為什麼需要無鎖操作1 2. 硬體支持 cas atomic2 3. 無鎖編程(Lock-Free)就是在某些應用場景和領域下解決以上基於鎖機制的併發編程的一種方案。3 4. Volatile 記憶體屏障(Memory ...
  • 中介者要解決的問題 中介者模式(Mediator Pattern)是用來降低多個對象和類之間通信的複雜度。這種模式提供了一個中介類,該類通常用來處理不同類之間的通信。中介者模式符合迪米特原則,即一個類應當儘量減少與其他類的聯繫。 實例描述 在我們現實生活中就有這樣的例子,你如果去租房,你可能會在網站 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...