C# 中使用面向切面编程(AOP)中实践代码整洁

1. 前言

最近在看《架构整洁之道》一书,书中反复提到了面向对象编程的 SOLID 原则(在作者的前一本书《代码整洁之道》也是被大力阐释),而面向切面编程(Aop)作为面向对象编程的有力补充,对实践整洁代码更是如虎添翼。

除了整洁之道系列的影响外,本文还致敬、借鉴、补充了Aspect Oriented Programming (AOP) in C# with SOLID一文。

1. Aop 是什么?

在讨论 Aop 之前,我们可以先看看一段很常见的代码。

640?wx_fmt=png

这是一段很典型的面向过程的代码,我们可以看到有相同的异常处理逻辑,如果我们想要避免重复的代码,我们至少可以把异常处理封装一下:

640?wx_fmt=png

代码简洁了很多,但是我们实际上是将真实的方法代码与日志代码纠缠在一起,违反了 单一责任原则 。有没有一种可能,让我们不需要在原来的代码上显式调用 TryCache 呢?

一个可能的答案是借助 AOP 来解决。使用 AOP,我们可以在不改变原来代码的前提下,添加额外的单元功能(如异常处理,日志处理、重试机制等)。 AOP 可以把原来一大串的面向过程的代码重构成多个部分,聚焦于每一小部分,使我们的代码 可读性 和 维护性 更高,避免了 代码重复和代码纠缠 的问题。

2. 装饰器实现 AOP

C# 可以使用的 Aop 框架有很多,在我们谈论他们之前,我们可以先利用语言自带的特性,实现基础的 AOP 效果。 最简单的形式莫过于 装饰器模式 ,它的雏形大致如下:

640?wx_fmt=png

可以看到装饰器只是在原来的对象上面扩展,符合 开放封闭原则。我们在调用的时候,只需显式创建装饰实例对象。

 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 方法来处理代理的逻辑。

640?wx_fmt=png

在调用的时候,类似装饰器一样需要创建代理实例。

640?wx_fmt=png

有很多开源项目在使用 Castle DynamicProxy,其稳定性和可靠性是值得信赖的,更多的使用方法可以参照官方示例或者第三方开源项目的代码。需要特别注意的是,Castle DynamicProxy 只能作用于接口或者虚方法,这是动态代理的特性(局限)。

除了 Castle DynamicProxy 外, AspectCore也是一个不错的选择。AspectCore 的快速简单应用通过继承 AbstractInterceptorAttribute 的 Attribute类来标记并拦截代理对应的接口或者虚方法(更详细的用法可以参考 作者写的使用方法)。

640?wx_fmt=png

虽然易用性很好,但是要注意使用的场合,如果是在低层次(如基础设施层、应用入口层等)或者特定的应用模块内使用,对整体架构影响不大。如果是在高层次(逻辑层、核心层、领域层等)使用,则会带来不必要的依赖污染

所以并不是推荐使用这种 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 类,然后标记在特定的类方法上面。

640?wx_fmt=png

最后还需要一个 AssemblyInfo.cs 来配置哪些 Attribute 类产生作用。

//AssemblyInfo.csusing System;
[module: FodyTest]

重新编译生成,在输出中还可以看到 Fody 的输出。
640?wx_fmt=png

既然我们可以在编译时织入 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

640?wx_fmt=jpeg

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/319299.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

AtCoder Beginner Contest 183 总结

本来懒得写了&#xff0c;不过第一次AK还是记录一下吧 A - ReLU ABC的签到题就是友好 #define IO ios::sync_with_stdio(false);cin.tie();cout.tie(0) #pragma GCC optimize(2) #include<set> #include<map> #include<cmath> #include<stack> #inc…

【数论】ZAP-Queries(P3455)

正题 P3455 题目大意 有T组询问&#xff0c;每组询问给出n,m,c&#xff0c;求∑i1n∑j1m[(i,j)c]\sum_{i1}^{n}\sum_{j1}^{m}[(i,j)c]i1∑n​j1∑m​[(i,j)c] 解题思路 可以先对n,m除c这样就使得求出的数都有c的因子&#xff0c;得到式子如下 ∑i1n∑j1m[(i,j)1]∑i1n∑j1m∑…

AT4518-[AGC032C]Three Circuits【欧拉回路】

正题 题目链接:https://www.luogu.com.cn/problem/AT4518 题目大意 给出nnn个点mmm条边的一张简单无向联通图&#xff0c;求能否把它分成三个可重复点的环。 1≤n,m≤1051\leq n,m\leq 10^51≤n,m≤105 解题思路 相当于你要去掉图上的两个环后依旧有欧拉回路 首先原本肯定得…

ABB (2020牛客国庆集训派对day1)

ABB 题意&#xff1a; 长度为n的字符串&#xff0c;问最少添加多少字符可以使其构成回文字符串 题解&#xff1a; 最长回文字符串我的第一反应是manacher马拉车算法&#xff0c;那我们直接马拉车找到已有最长回文串&#xff0c;然后总长度减去不就是答案吗&#xff1f;非也…

微软对开发者献真爱,全面支持开源,加速研发云升级

全球首秀&#xff1a;支持 Java 和 Python 的 Visual Studio Live Share 协作服务与 IntelliCode 人工智能辅助编程&#xff0c;适用于物联网和智能边缘的自动机器学习能力&#xff08;Auto ML&#xff09;&#xff1b;Visual Studio App Center 提供一站式跨平台移动应用开发体…

Codeforces Round #681 (Div. 2, based on VK Cup 2019-2020 - Final)

今天看不下去数电vp一场div2&#xff0c;搞A搞了很长时间&#xff0c;导致后面没有时间写&#xff0c;不过今天补题的时候全是独立补出来的没有看题解 vp3题&#xff0c;补3题 A - Kids Seating 最开始想的是与质数有关&#xff0c;乱七八糟搞了半天&#xff0c;结果最后回头…

【数论】GCD(P2568)

正题 P2568 题目大意 求满足1≤x,y≤n1\leq x,y\leq n1≤x,y≤n且gcd(x,y)primegcd(x,y)primegcd(x,y)prime的数对(x,y)(x,y)(x,y)的个数 解题思路 题目即求 ∑i1n∑j1n[gcd(i,j)prime]\sum_{i1}^n\sum_{j1}^n[gcd(i,j)prime]i1∑n​j1∑n​[gcd(i,j)prime] 可以考虑先枚举…

AT4502-[AGC029C]Lexicographic constraints【二分,栈】

正题 题目链接:https://www.luogu.com.cn/problem/AT4502 题目大意 给出nnn个长度SSS&#xff0c;求一个最小mmm表示用大小为mmm的字符集构造出nnn个符合对应长度的字符串使得字符串按照给出顺序从小到大。 1≤n≤2105,1≤Si≤1091\leq n\leq 2\times 10^5,1\leq S_i\leq 10^…

2020牛客国庆集训派对day1 C. Bob in Wonderland

Bob in Wonderland 题意&#xff1a; 一棵树&#xff0c;问最少移动多少次边可以使其变成一个链&#xff1f; 移动是指&#xff1a;从原位置拆下并连到新位置&#xff0c;这样算一次 题解&#xff1a; 错误思路 我一开始在想既然求最少移动次数&#xff0c;那我们就尽可能…

Ocelot简易教程(七)之配置文件数据库存储插件源码解析

上篇文章Ocelot简易教程&#xff08;六&#xff09;之重写配置文件存储方式并优化响应数据 给大家分享了如何集成我写的一个Ocelot扩展插件把Ocelot的配置存储到数据库中。并没有对实现原理进行相应的阐述。今天抽空把实现的原理给大家说道说道。明白原理后&#xff0c;大家就可…

【数论】YY的GCD(P2257)

正题 P2257 题目大意 给你T组询问&#xff0c;每组询问给出n,m&#xff0c;让你求 1≤x≤n,1≤y≤m1\leq x\leq n,1\leq y\leq m1≤x≤n,1≤y≤m 且 gcd(x,y)primegcd(x,y)primegcd(x,y)prime 的方案数 解题思路 根据题意&#xff0c;有 ∑i1n∑j1m[gcd(i,j)prime]\sum_{i1}…

ASP.NET Core 2调用Azure云上的PowerBI报表展示

在开发企业应用中&#xff0c;报表功能是当之无愧的重头戏&#xff0c;如何将数据通过合适的报表呈现出来成为每个项目人员必需面临的问题。而找到一款合适的报表往往都需要考率价格、开发、风格、支撑等因素。那么&#xff0c;我在这里给大家介绍一款由微软提供的可视化报表工…

2020牛客国庆集训派对day1 Zeldain Garden

Zeldain Garden 题意&#xff1a; 问[L,R]内所有数的因子的数量和 题解&#xff1a; 如果传统暴力做肯定不行 我们来找找规律&#xff1a; 数字&#xff1a; 因子数目 1~n的因子数和 1 1 1 2 2 321/ 3 2 5311 4 3 84211 5 2 1052111 6 4 14 7 2 16 8…

CF585E-Present for Vitalik the Philatelist【莫比乌斯反演,狄利克雷前缀和】

正题 题目链接:https://www.luogu.com.cn/problem/CF585E 题目大意 给出一个大小为nnn的可重集TTT&#xff0c;求有多少个它的非空子集SSS和元素xxx满足 x∉S,gcd{S}>1,gcd(S,x)1x\notin S,gcd\{S\}>1,gcd(S,x)1x∈/​S,gcd{S}>1,gcd(S,x)1 1≤n≤51051\leq n\leq 5…

ABC182——F - Valid payments Editorial

F - Valid payments Editorial 大佬题解 看了述题解我才刚理解题目意思。 Here, both Lunlun and the clerk used the minimum number of coins needed to represent those amounts of money. 这句话意味着任何数都能用a1…ana_1\dots a_na1​…an​这些面值的货币唯一表示 并…

【数论】GCD SUM(P2398)

正题 P2398 题目大意 给出n&#xff0c;求∑i1n∑j1ngcd(i,j)\sum_{i1}^n\sum_{j1}^ngcd(i,j)i1∑n​j1∑n​gcd(i,j) 解题思路 考虑先枚举gcd&#xff0c;那么有 ∑d1nd∑i1n/d∑j1n/d[gcd(i,j)1]\sum_{d1}^nd\sum_{i1}^{n/d}\sum_{j1}^{n/d}[gcd(i,j)1]d1∑n​di1∑n/d​j…

AT4519-[AGC032D]Rotation Sort【dp】

正题 题目链接:https://www.luogu.com.cn/problem/AT4519 题目大意 给出一个长度为nnn的排列&#xff0c;每次可以选择一个区间&#xff0c;然后花费AAA的代价向左旋转&#xff08;最左边的丢到最右边&#xff09;或者花费BBB的代价向右旋转。 排升序序的最小花费。 1≤n≤50…

2020牛客国庆集训派对day2 VIRUS OUTBREAK

VIRUS OUTBREAK 文章目录题目描述输入描述:输出描述:题解&#xff1a;代码&#xff1a;题目描述 The State Veterinary Services Department recently reported an outbreak of a newly found cow disease. All cows found to have affected by the disease have since euthan…

Codeforces Round #684 (Div. 2)

A - Buy the String 要么全变成1要么全变成0要么一个都不改变&#xff0c;三种情况取最小。 #define IO ios::sync_with_stdio(false);cin.tie();cout.tie(0) #pragma GCC optimize(2) #include<set> #include<map> #include<cmath> #include<stack>…

聊聊如何设计千万级吞吐量的.Net Core网络通信!

作者&#xff1a;大石头时间&#xff1a;2018-10-26 晚上 20:00地点&#xff1a;QQ群-1600800内容&#xff1a;网络通信&#xff0c;网络库使用方式网络库设计理念&#xff0c;高性能要点介绍首先看下面这张很具有代表性的图&#xff0c;2018年5月份做的测试。当时单服务器得到…