C# 中使用面向切面编程(AOP)中实践代码整洁
1. 前言
最近在看《架構整潔之道》一書,書中反復提到了面向對象編程的?SOLID 原則(在作者的前一本書《代碼整潔之道》也是被大力闡釋),而面向切面編程(Aop)作為面向對象編程的有力補充,對實踐整潔代碼更是如虎添翼。
除了整潔之道系列的影響外,本文還致敬、借鑒、補充了Aspect Oriented Programming (AOP) in C# with SOLID一文。
1. Aop 是什么?
在討論 Aop 之前,我們可以先看看一段很常見的代碼。
這是一段很典型的面向過程的代碼,我們可以看到有相同的異常處理邏輯,如果我們想要避免重復的代碼,我們至少可以把異常處理封裝一下:
代碼簡潔了很多,但是我們實際上是將真實的方法代碼與日志代碼糾纏在一起,違反了?單一責任原則?。有沒有一種可能,讓我們不需要在原來的代碼上顯式調用 TryCache 呢?
一個可能的答案是借助 AOP 來解決。使用 AOP,我們可以在不改變原來代碼的前提下,添加額外的單元功能(如異常處理,日志處理、重試機制等)。 AOP 可以把原來一大串的面向過程的代碼重構成多個部分,聚焦于每一小部分,使我們的代碼?可讀性?和?維護性?更高,避免了?代碼重復和代碼糾纏?的問題。
2. 裝飾器實現 AOP
C# 可以使用的 Aop 框架有很多,在我們談論他們之前,我們可以先利用語言自帶的特性,實現基礎的 AOP 效果。 最簡單的形式莫過于?裝飾器模式?,它的雛形大致如下:
可以看到裝飾器只是在原來的對象上面擴展,符合?開放封閉原則。我們在調用的時候,只需顯式創建裝飾實例對象。
var tryClient=new TryHandler<MyClient>(new MyClient());tryClient.GetOtherOne();細心的讀者可能還會發現,我們還可以在這個日志裝飾器上面再附加一個裝飾器,比如一個針對結果處理的裝飾器。
var resultClient=new ResultHandler<TryHandler<MyClient>>(tryClient);但是這樣的調用方法還是不盡人意,想象如果某個對象有三四個裝飾器,那么我們創建實例的時候就需要多次傳遞。一個解決方法是?借助依賴注入 (DI)?,只需注冊一次服務類型,避免通過創建實例來獲取對象。另外,對于 .net core自帶的 DI 來說,更便捷的方法是借助開源類庫Scrutor?來注冊裝飾器對象。
services.Decorate<IMyClient, TryHandler<MyClient(); services.Decorate<IMyClient, ResultHandler<MyClient>>();雖然解決了易用性,但是我們很快就發現了另一些不盡人意的地方,裝飾器模式只能適用于?特定的類型,約束是比較強的。如果我們希望我們示例中的裝飾器可以實現通用,就需要找別的方法了。
3. 動態代理實現 Aop
動態代理是指運行時生成,通過隱式重寫方法來附加額外的功能,而其中最流行的莫過于?Castle DynamicProxy了。
Castle DynamicProxy 的常規用法是繼承?IInterceptor?接口,通過實現?Intercept?方法來處理代理的邏輯。
在調用的時候,類似裝飾器一樣需要創建代理實例。
有很多開源項目在使用 Castle DynamicProxy,其穩定性和可靠性是值得信賴的,更多的使用方法可以參照官方示例或者第三方開源項目的代碼。需要特別注意的是,Castle DynamicProxy 只能作用于接口或者虛方法,這是動態代理的特性(局限)。
除了 Castle DynamicProxy 外,?AspectCore也是一個不錯的選擇。AspectCore 的快速簡單應用通過繼承?AbstractInterceptorAttribute?的 Attribute類來標記并攔截代理對應的接口或者虛方法(更詳細的用法可以參考?作者寫的使用方法)。
雖然易用性很好,但是要注意使用的場合,如果是在低層次(如基礎設施層、應用入口層等)或者特定的應用模塊內使用,對整體架構影響不大。如果是在高層次(邏輯層、核心層、領域層等)使用,則會帶來不必要的依賴污染。
所以并不是推薦使用這種 Attribute 攔截代理的方式,好在 AspectCore 的設計考慮到解耦的需要,可以在單獨配置代理攔截。
serviceCollection.ConfigureDynamicProxy(config =>{config.Interceptors.AddTyped<CustomInterceptorAttribute>(Predicates.ForMethod("ICustomService", "Call")); });但是不管是 Castle DynamicProxy 還是 AspectCore 都只能作用與接口或者虛方法,這也是動態代理的局限(特性)。如果我們想要在不受限制地在非虛方法上實現 AOP 的效果,就需要別的方法了。
4. 編譯時織入實現 AOP
進行 AOP 的另一種方法是通過編譯時織入,在編譯的程序集內部的方法中添加額外的?IL 代碼,附加我們想要的功能。
PostSharp?是其中比較流行的一種,然而由于其商業化的性質,在這里不做過多介紹。開源方面,Fody?是其中的佼佼者。
Fody 在編譯時使用?Mono.Cecil?修改 . net 程序集的 IL 代碼。如果你沒有 IL 代碼方面的知識,可以直接使用基于 Fody 開發的插件。其中最流行的插件是Costura和?Virtuosity。Costura 將依賴項作為資源嵌入,實現多個 DLL 文件合并成一個 exe?的功能,而 Virtuosity 則是在構建的時候將所有成員更改為?virtual?,重寫 ORM (如EF的導航屬性、NHibernate)、 Mock(RhinoMocks、NMock)以及前面提到的動態代理中需要?virtual?的地方為?virtual。
Fody 中的插件還有很多,除了 Costura 和 Virtuosity 之外,我個人還使用過?MethodDecorator,實現編譯時重寫類的方法或者構造函數來實現 AOP 的效果。
所有 Fody 的插件,首先都必須引入一個?FodyWeavers.xml?,并聲明使用的插件。
xml version="1.0" encoding="utf-8"<!--FodyWeavers.xml--><Weavers><MethodDecorator /></Weavers>不同的插件在后面的使用方法會有所不同,以 MethodDecorator 為例,我們需要新建一個特定格式的 Attribute 類,然后標記在特定的類方法上面。
最后還需要一個?AssemblyInfo.cs?來配置哪些 Attribute 類產生作用。
//AssemblyInfo.csusing System; [module: FodyTest]重新編譯生成,在輸出中還可以看到 Fody 的輸出。
既然我們可以在編譯時織入 IL 代碼,那么我們是不是可以提前生成我們想要的 AOP 效果,比如說借助代碼生成器。
5. 代碼生成器實現 AOP 效果
T4是常見的文本生成框架,我們可以使用此工具在設計時生成代碼。前面我們提到過裝飾器模式有特異性的問題,只能針對特定類型實現 AOP 效果,而借助代碼生成器,我們可以直接生成對應的代碼模板,避免了重復的勞動。由于我個人對 T4 沒什么使用經驗,有興趣的讀者可以參考Aspect Oriented Programming (AOP) in C# via T4一文。
除了 T4 之外,Roslyn?也是一個強有力的工具,已經有人基于 Roslyn 實現 AOP 的效果,將 Roslyn 封裝為?dotnet 全局工具?,針對特定的文件插入指定的代碼段,有興趣的讀者可以參考?AOP_With_Roslyn?的代碼示例。
結語
AOP 是我們?避免代碼重復?和?增強代碼可讀性?的有力工具,是我們編寫整潔代碼的有力保證,借助 C# 語言自身的特性和諸多強大的開源工具,使我們更專注于代碼功能。
原文地址:?https://www.cnblogs.com/chenug/p/9848852.html
.NET社區新聞,深度好文,歡迎訪問公眾號文章匯總 http://www.csharpkit.com
總結
以上是生活随笔為你收集整理的C# 中使用面向切面编程(AOP)中实践代码整洁的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 微软官宣:史上最贵开发工具 75亿美金收
- 下一篇: 终于明白了 C# 中 Task.Yiel