巧用攔截器:高效的擴展點設計


最近在設計框架時,需要設計一類擴展點,發現不能簡單地繼承或使用事件來給使用者提供 API。最終使用攔截器模式解決了 API 的設計。

 

擴展點使用場景


該擴展點的使用場景如下:

  1. 不能使用繼承;需要在類型的繼承體系外(非被擴展類型的子類)對類型進行擴展。
  2. 需要能在基本邏輯的執行前、后擴展新的邏輯,甚至可以使用新的邏輯替換基礎邏輯。
  3. 對於性能敏感。由於該基礎邏輯是比較核心的代碼,需要盡量地減少擴展點帶來的額外性能消耗、並盡量少地產生額外的對象。

 

擴展點設計方案選型


在框架設計時,擴展點設計主要通過幾種類型的 API 來提供:虛方法、事件、接口。(關於擴展點的設計,詳細的內容,參見:《Framework Design Guidelines 2nd Edition》第六章,擴展性設計。)而最常用、最方便使用者使用的擴展點則是前兩個:虛方法和事件。

前兩種擴展點設計方案的主要區別在於:

  1. 場景:繼承中的虛方法主要是為類型的子類型進行擴展而提供的,而事件則主要是為繼承體系外的類型來擴展繼承體系內的類型的行為而提供的;
  2. 控制度:子類對虛方法進行重寫時,可以在基類的基本方法前、后編寫自己的擴展代碼,同時還可以控制是否需要調用基類的方法;而事件要實現這些功能,需要提供邏輯前事件(Invoking)、邏輯后事件(Invoked),並通過類似 CancelEventArgs.IsCancel 屬性等方式來控制是否需要執行基本的邏輯。
  3. 性能:虛方法的調用是非常高效的,也不會產生額外的對象。而事件的機制本質是委托列表,會遍歷該列表進行調用,同時需要產生額外的委托對象;其次,由於 .NET 事件的設計中往往要求提供一個從 EventArgs 類型上繼承的事件參數對象,在每次調用都構造並傳遞該對象,這也會產品額外的對象壓力。

可以看出,如果是想設計一類提供給繼承體系外類型進行擴展的擴展點, 虛方法、事件兩類 API 都不合適。那我們只能在第三種方式上想辦法:接口。接口的設計則非常靈活,而其實我上面描述的場景會經常遇到,所以應該提取出一類設計模式。經過一番思考,我發現其實攔截器模式比較適合該場景。攔截器模式本身注重對消息、方法的攔截處理,是一種繼承體系外的擴展方法,並被大量用於 AOP 的實現。在這里采用該模式,我們更加關注其在真正核心方法調用前后的擴展機制、以及核心方法的阻斷機制,以及最終擴展 API 提供的形式。

 

實現


該模式放到 Rafy 中實現提交時的擴展點后,類圖如下:

Interceptor

 

擴展點使用方法也較簡單,使用者繼承攔截器,編寫相應的擴展邏輯即可:

image

有一個細節需要注意:上圖中能看到,方法的第一個參數也是一個自定義的參數類型 SubmitArgs。但是由於攔截器是一種鏈式調用,所以這個類型可以采用值類型;在此方法被大量調用時,相對於事件的擴展機制,沒有大量的冗余對象。

在啟動時,加入以下代碼就可以把該攔截器添加到保存的攔截器列表中:

image

 

總結


攔截器模式實現起來比較簡單,該模式的結構非常類似於 GOF 中的職責鏈模式,只是關注點不同。在使用它作為擴展點時,對於使用者來說也比較易用,而且性能相對於事件機制來說更好,所以可以直接作為一種常用的擴展點設計方案。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM