面向方面编程(AOP)
简介
如果你很熟悉面向方面编程(AOP),你就会知道给代码增加“切面”可以使代码更清晰并且具有可维护性。但是AOP通常都依赖于第三方类库或者硬编码的.net特性来工作。虽然这些实现方式的好处大于它们的复杂程度,但是我仍然在寻找一种实现AOP的更为简单的方式,来试我的代码更为清晰。我将它们单独移出来,并命名为AspectF。
Aspect Oriented Programming (AOP)的背景
“切面”指的是那些在你写的代码中在项目的不同部分且有相同共性的东西。它可能是你代码中处理异常、记录方法调用、时间处理、重新执行一些方法等等的一些特殊方式。如果你没有使用任何面向切面编程的类库来做这些事情,那么在你的整个项目中将会遗留一些很简单而又重复的代码,它将使你的代码很难维护。例如,在你的业务逻辑层有些方法需要被记录,有些异常需要被处理,有些执行需要计时,数据库操作需要重试等等。所以,也许你会写出下面这样的代码。
- public bool InsertCustomer(string firstName, string lastName, int age,
 - Dictionary<string, string> attributes)
 - {
 - if (string.IsNullOrEmpty(firstName))
 - throw new ApplicationException("first name cannot be empty");
 - if (string.IsNullOrEmpty(lastName))
 - throw new ApplicationException("last name cannot be empty");
 - if (age < 0)
 - throw new ApplicationException("Age must be non-zero");
 - if (null == attributes)
 - throw new ApplicationException("Attributes must not be null");
 - // Log customer inserts and time the execution
 - Logger.Writer.WriteLine("Inserting customer data...");
 - DateTime start = DateTime.Now;
 - try
 - {
 - CustomerData data = new CustomerData();
 - bool result = data.Insert(firstName, lastName, age, attributes);
 - if (result == true)
 - {
 - Logger.Writer.Write("Successfully inserted customer data in "
 - + (DateTime.Now-start).TotalSeconds + " seconds");
 - }
 - return result;
 - }
 - catch (Exception x)
 - {
 - // Try once more, may be it was a network blip or some temporary downtime
 - try
 - {
 - CustomerData data = new CustomerData();
 - if (result == true)
 - {
 - Logger.Writer.Write("Successfully inserted customer data in "
 - + (DateTime.Now-start).TotalSeconds + " seconds");
 - }
 - return result;
 - }
 - catch
 - {
 - // Failed on retry, safe to assume permanent failure.
 - // Log the exceptions produced
 - Exception current = x;
 - int indent = 0;
 - while (current != null)
 - {
 - string message = new string(Enumerable.Repeat('\t', indent).ToArray())
 - + current.Message;
 - Debug.WriteLine(message);
 - Logger.Writer.WriteLine(message);
 - current = current.InnerException;
 - indent++;
 - }
 - Debug.WriteLine(x.StackTrace);
 - Logger.Writer.WriteLine(x.StackTrace);
 - return false;
 - }
 - }
 - }
 
你会看到上面只有两行关键代码,它调用了CustomerData实例的一个方法插入了一个Customer。但去实现这样的业务逻辑,你真的很难去照顾所有的细节(日志记录、重试、异常处理、操作计时)。项目越成熟,在你的代码中需要维护的这些“边边角角”就更多了。所以你肯定经常会到处拷贝这些“样板”代码,但只在这些样板内写少了真是的东西。这多不值!你不得不对每个业务逻辑层的方法都这么做。比如现在你想在你的业务逻辑层中增加一个UpdateCustomer方法。你不得不再次拷贝所有的这些“样板”,然后将两行关键代码加入其中。
思考这样的场景,你需要做出一个项目级别的改变——针对如何处理异常。你不得不处理你写的这“上百”的方法,然后一个一个地修改它们。如果你想修改计时的逻辑,做法同样如此。
面向切面编程就可以很好地处理这些问题。当你采用AOP,你会以一种很酷的方式来实现它:
- [EnsureNonNullParameters]
 - [Log]
 - [TimeExecution]
 - [RetryOnceOnFailure]
 - public void InsertCustomerTheCoolway(string firstName, string lastName, int age,
 - Dictionary<string, string> attributes)
 - {
 - CustomerData data = new CustomerData();
 - data.Insert(firstName, lastName, age, attributes);
 - }
 
这里你需要区分这些通用的东西,像日志记录、计时、重试、验证等这些通常被称为“边边角角”的东西,最重要的是完全与你的“真实”代码无关。这可以使方法将会变得美观而清晰。所有的这些细节都在方法外被处理,并且只是在代码外加上了一些属性。这里,每一个属性代表一个Aspect(切面)。例如,你可以增加“日志记录”切面到任何代码中,只需要增加一个Log属性。无论你使用何种AOP的类库,该类库都能够确保这些“切面”被有效地加入到代码中,当然时机不一,可能是在编译时,也可能是在运行时。
有许多AOP类库通过使用编译事件和IL操作允许你在编译时“处理”这些方面,例如PostSharp;而某些类库使用DynamicProxy在运行时处理;某些要求你的类继承自ContextBoundObject使用C#内建特性来supportAspects。所有的这些都有某些“不便”。你不得不使用某些外部库,做足够的性能测试来那些类库可扩展等等。而你需要的只是一个非常简单的方式来实现“隔离”,可能并不是想要完全实现AOP。记住,你的目的是隔离那些并不重要的核心代码,来让一切变得简单并且清晰!
AspectF如何来让这一切变得简单!
让我展示一种简答的方式来实现这种隔离,仅仅使用标准的C#代码,类和代理的简单调用,没有用到“特性”或者“IL操作”这些东西。它提供了可重用性和可维护性。最好的一点是它的“轻量级”——仅仅一个很小得类。
- public void InsertCustomerTheEasyWay(string firstName, string lastName, int age,
 - Dictionary<string, string> attributes)
 - {
 - AspectF.Define
 - .Log(Logger.Writer, "Inserting customer the easy way")
 - .HowLong(Logger.Writer, "Starting customer insert",
 - "Inserted customer in {1} seconds")
 - .Retry()
 - .Do(() =>
 - {
 - CustomerData data = new CustomerData();
 - data.Insert(firstName, lastName, age, attributes);
 - });
 - }
 
让我们看看它与通常的AOP类库有何不同:
(1) 不在方法的外面定义“切面”,而是在方法的内部直接定义。
(2) 取代将“切面”做成类,而是将其构建成方法
现在,看看它有什么优势:
(1) 没有很“深奥”的要求(Attributes, ContextBoundObject, Post build event, IL Manipulation,DynamicProxy)
(2) 没有对其他依赖的性能担忧
(3) 直接随意组合你要的“切面”。例如,你可以只对日志记录一次,但尝试很多次操作。
(4) 你可以传递参数,局部变量等到“切面”中,而你在使用第三方类库的时候,通常不能这么做
(5) 这不是一个完整的框架或类库,而仅仅是一个叫做AspectF的类
(6) 可能以在代码的任何地方定义方面,例如你可以将一个for 循环包裹成一个“切面”
让我们看看使用这种方案构建一个“切面”有多简单!这个方案中“切面”都是以方法来定义的。AspectExtensions类包含了所有的这些“预构建”的切面,比如:Log、Retry、TrapLog、TrapLogThrow等。例如,这里展示一下Retry是如何工作的:
- [DebuggerStepThrough]
 - public static AspectF Retry(this AspectF aspects)
 - {
 - return aspects.Combine((work) =>
 - Retry(1000, 1, (error) => DoNothing(error), DoNothing, work));
 - }
 - [DebuggerStepThrough]
 - public static void Retry(int retryDuration, int retryCount,
 - Action<Exception> errorHandler, Action retryFailed, Action work)
 - {
 - do
 - {
 - try
 - {
 - work();
 - }
 - catch (Exception x)
 - {
 - errorHandler(x);
 - System.Threading.Thread.Sleep(retryDuration);
 - }
 - } while (retryCount-- > 0);
 - retryFailed();
 - }
 
你可以让“切面”调用你的代码任意多次。很容易在Retry切面中包裹对数据库、文件IO、网络IO、Web Service的调用,因为它们经常由于各种基础设施问题而失败,并且有时重试一次就可以解决问题。我有个习惯是总是去尝试数据库插入,更新,删除、web service调用,处理文件等等。而这样的“切面”无疑让我对处理这样的问题时轻松了许多。
下面展示了一下它是如何工作的,它创建了一个代理的组合。而结果就像如下这段代码:
- Log(() =>
 - {
 - HowLong(() =>
 - {
 - Retry(() =>
 - {
 - Do(() =>
 - {
 - CustomerData data = new CustomerData();
 - data.Insert(firstName, lastName, age, attributes);
 - });
 - });
 - });
 - });
 
AspectF类除了压缩这样的代码之外,其他什么都没有。
下面展示,你怎样创建你自己的“切面”。首先为AspectF类创建一个扩展方法。比如说,我们创建一个Log:
- [DebuggerStepThrough]
 - public static AspectF Log(this AspectF aspect, TextWriter logWriter,
 - string beforeMessage, string afterMessage)
 - {
 - return aspect.Combine((work) =>
 - {
 - logWriter.Write(DateTime.Now.ToUniversalTime().ToString());
 - logWriter.Write('\t');
 - logWriter.Write(beforeMessage);
 - logWriter.Write(Environment.NewLine);
 - work();
 - logWriter.Write(DateTime.Now.ToUniversalTime().ToString());
 - logWriter.Write('\t');
 - logWriter.Write(afterMessage);
 - logWriter.Write(Environment.NewLine);
 - });
 - }
 
你调用AspectF的Combine方法来压缩一个将要被放进委托链的委托。委托链在最后将会被Do方法调用。
- public class AspectF
 - {
 - /// <summary>
 - /// Chain of aspects to invoke
 - /// </summary>
 - public Action<Action> Chain = null;
 - /// <summary>
 - /// Create a composition of function e.g. f(g(x))
 - /// </summary>
 - /// <param name="newAspectDelegate">A delegate that offers an aspect's behavior.
 - /// It's added into the aspect chain</param>
 - /// <returns></returns>
 - [DebuggerStepThrough]
 - public AspectF Combine(Action<Action> newAspectDelegate)
 - {
 - if (this.Chain == null)
 - {
 - this.Chain = newAspectDelegate;
 - }
 - else
 - {
 - Action<Action> existingChain = this.Chain;
 - Action<Action> callAnother = (work) =>
 - existingChain(() => newAspectDelegate(work));
 - this.Chain = callAnother;
 - }
 - return this;
 - }
 
这里Combine方法操作的是被“切面”扩展方法传递过来的委托,例如Log,然后它将该委托压入之前加入的一个“切面”的委托中,来保证第一个切面调用第二个,第二个调用第三个,知道最后一个调用真实的(你想要真正执行的)代码。
Do/Return方法做最后的执行操作。
- /// <summary>
 - /// Execute your real code applying the aspects over it
 - /// </summary>
 - /// <param name="work">The actual code that needs to be run</param>
 - [DebuggerStepThrough]
 - public void Do(Action work)
 - {
 - if (this.Chain == null)
 - {
 - work();
 - }
 - else
 - {
 - this.Chain(work);
 - }
 - }
 
就是这些,现在你有一个非常简单的方式来分隔那些你不想过度关注的代码,并使用C#享受AOP风格的编程模式。
AspectF类还有其他几个方便的“切面”,大致如下(当然你完全可以DIY你自己的‘切面’)。
- public static class AspectExtensions
 - {
 - [DebuggerStepThrough]
 - public static void DoNothing()
 - {
 - }
 - [DebuggerStepThrough]
 - public static void DoNothing(params object[] whatever)
 - {
 - }
 - [DebuggerStepThrough]
 - public static AspectF Delay(this AspectF aspect, int milliseconds)
 - {
 - return aspect.Combine((work) =>
 - {
 - System.Threading.Thread.Sleep(milliseconds);
 - work();
 - });
 - }
 - [DebuggerStepThrough]
 - public static AspectF MustBeNonNull(this AspectF aspect, params object[] args)
 - {
 - return aspect.Combine((work) =>
 - {
 - for (int i = 0; i < args.Length; i++)
 - {
 - object arg = args[i];
 - if (arg == null)
 - {
 - throw new ArgumentException(string.Format("Parameter at index {0} is null", i));
 - }
 - }
 - work();
 - });
 - }
 - [DebuggerStepThrough]
 - public static AspectF MustBeNonDefault<T>(this AspectF aspect, params T[] args) where T : IComparable
 - {
 - return aspect.Combine((work) =>
 - {
 - T defaultvalue = default(T);
 - for (int i = 0; i < args.Length; i++)
 - {
 - T arg = args[i];
 - if (arg == null || arg.Equals(defaultvalue))
 - {
 - throw new ArgumentException(string.Format("Parameter at index {0} is null", i));
 - }
 - }
 - work();
 - });
 - }
 - [DebuggerStepThrough]
 - public static AspectF WhenTrue(this AspectF aspect, params Func<bool>[] conditions)
 - {
 - return aspect.Combine((work) =>
 - {
 - foreach (Func<bool> condition in conditions)
 - {
 - if (!condition())
 - {
 - return;
 - }
 - }
 - work();
 - });
 - }
 - [DebuggerStepThrough]
 - public static AspectF RunAsync(this AspectF aspect, Action completeCallback)
 - {
 - return aspect.Combine((work) => work.BeginInvoke(asyncresult =>
 - {
 - work.EndInvoke(asyncresult); completeCallback();
 - }, null));
 - }
 - [DebuggerStepThrough]
 - public static AspectF RunAsync(this AspectF aspect)
 - {
 - return aspect.Combine((work) => work.BeginInvoke(asyncresult =>
 - {
 - work.EndInvoke(asyncresult);
 - }, null));
 - }
 - }
 
现在,你已经拥有了一个简洁的方式来隔离那些细枝末节的代码,去享受AOP形式的编程而无需使用任何“笨重”的框架。
面向方面编程(AOP)的更多相关文章
- 黑马----面向方面编程AOP
		
黑马程序员:Java培训.Android培训.iOS培训..Net培训 JAVA反射-面向方面编程AOP 一.面向方面的需求 有如下模型: 需要统计客户登录时间.使用系统情况,或系统运行日记等信息时, ...
 - Web静态和动态项目委托代理基于面向方面编程AOP
		
本来每天更新,我一般喜欢晚上十二点的时候发文章,结果是不是愚人节?校内网也将是非常有趣,破,把我给打. ..好吧-从今天开始的话题AOP.AOP太重要了,所以把第二篇文章谈论这个话题,AOP它是Spr ...
 - 设计模式之面向切面编程AOP
		
动态的将代码切入到指定的方法.指定位置上的编程思想就是面向切面的编程. 代码只有两种,一种是逻辑代码.另一种是非逻辑代码.逻辑代码就是实现功能的核心代码,非逻辑代码就是处理琐碎事务的代码,比如说获取连 ...
 - Spring学习手札(二)面向切面编程AOP
		
AOP理解 Aspect Oriented Program面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术. 但是,这种说法有些片面,因为在软件工程中,AOP的价值体现的并 ...
 - Spring学习笔记:面向切面编程AOP(Aspect Oriented Programming)
		
一.面向切面编程AOP 目标:让我们可以“专心做事”,避免繁杂重复的功能编码 原理:将复杂的需求分解出不同方面,将公共功能集中解决 *****所谓面向切面编程,是一种通过预编译方式和运行期动态代理实现 ...
 - Spring框架学习笔记(2)——面向切面编程AOP
		
介绍 概念 面向切面编程AOP与面向对象编程OOP有所不同,AOP不是对OOP的替换,而是对OOP的一种补充,AOP增强了OOP. 假设我们有几个业务代码,都调用了某个方法,按照OOP的思想,我们就会 ...
 - Spring之控制反转——IoC、面向切面编程——AOP
		
控制反转——IoC 提出IoC的目的 为了解决对象之间的耦合度过高的问题,提出了IoC理论,用来实现对象之间的解耦. 什么是IoC IoC是Inversion of Control的缩写,译为控制 ...
 - 【串线篇】面向切面编程AOP
		
面向切面编程AOP 描述:将某段代码“动态”的切入到“指定方法”的“指定位置”进行运行的一种编程方式 (其底层就是Java的动态代理)spring对其做了简化书写 场景: 1).AOP加日志保存到数据 ...
 - 04 Spring:01.Spring框架简介&&02.程序间耦合&&03.Spring的 IOC 和 DI&&08.面向切面编程 AOP&&10.Spring中事务控制
		
spring共四天 第一天:spring框架的概述以及spring中基于XML的IOC配置 第二天:spring中基于注解的IOC和ioc的案例 第三天:spring中的aop和基于XML以及注解的A ...
 
随机推荐
- gridview XML
			
GridView动态添加模板列 http://blog.csdn.net/wljhk2006/article/details/24723219 XML与DataTable互转类 http://bl ...
 - 关于设置CFileDialog的默认路径
			
CFileDialog d_File(FRUE, NULL,NULL,NULL,szFilter,FromHandle(m_hWnd)); // 如果写了下面这句那么每次打开都是这个设置的默认路径 ...
 - VB VBA VBS有什么区别?
			
VB和VBA本就是同宗的姐妹,只不过姐姐VB的功夫要比妹妹VBA历害些.不过姐姐只会单打独斗是女强人:妹妹却只会傍大款(例如Office).姐姐有生育能力,是真正的女人:妹妹却不会生崽(生成.EXE) ...
 - 永久修改python默认的字符编码为utf-8
			
这个修改说来简单,其实不同的系统,修改起来还真不一样.下面来罗列下3中情况 首先所有修改的动作都是要创建一个叫 sitecustomize.py的文件,为什么要创建这个文件呢,是因为python在启动 ...
 - 朴素贝叶斯算法 & 应用实例
			
转载请注明出处:http://www.cnblogs.com/marc01in/p/4775440.html 引 和师弟师妹聊天时经常提及,若有志于从事数据挖掘.机器学习方面的工作,在大学阶段就要把基 ...
 - 第一百一十三节,JavaScript文档对象,DOM基础
			
JavaScript文档对象,DOM基础 学习要点: 1.DOM介绍 2.查找元素 3.DOM节点 4.节点操作 DOM(Document Object Model)即文档对象模型,针对HTML和XM ...
 - 如何提升 CSS 选择器性能
			
CSS 选择器性能损耗来自? CSS选择器对性能的影响源于浏览器匹配选择器和文档元素时所消耗的时间,所以优化选择器的原则是应尽量避免使用消耗更多匹配时间的选择器.而在这之前我们需要了解CSS选择器匹配 ...
 - bug记录_document.defaultview.getcomputedstyle()
			
页面中使用document.defaultview.getcomputedstyle()在火狐下取不到值. 原本方法现在$(document).ready()中,换到window.onload里就可以 ...
 - Docker私有仓库3
			
http://www.cnblogs.com/womars/p/5906435.html(上篇地址) 三.测试上两篇 #测试一下 [root@lh- data]# pwd /opt/data [roo ...
 - Python & virtualenv使用说明
			
virtualenv是virtual environment的缩写,可以创建独立的Python环境,用起来比较干净: 安装(已安装pip 或者 easy_install): 如果使用pip: pi ...