使用AOP 使C#代码更清晰 转yanghua_kobe

http://blog.csdn.net/yanghua_kobe/article/details/6917228

简介

如果你很熟悉面向方面编程(AOP),你就会知道给代码增加“切面”可以使代码更清晰并且具有可维护性。但是AOP通常都依赖于第三方类库或者硬编码的.net特性来工作。虽然这些实现方式的好处大于它们的复杂程度,但是我仍然在寻找一种实现AOP的更为简单的方式,来试我的代码更为清晰。我将它们单独移出来,并命名为AspectF。

Aspect Oriented Programming (AOP)的背景
“切面”指的是那些在你写的代码中在项目的不同部分且有相同共性的东西。它可能是你代码中处理异常、记录方法调用、时间处理、重新执行一些方法等等的一些特殊方式。如果你没有使用任何面向切面编程的类库来做这些事情,那么在你的整个项目中将会遗留一些很简单而又重复的代码,它将使你的代码很难维护。例如,在你的业务逻辑层有些方法需要被记录,有些异常需要被处理,有些执行需要计时,数据库操作需要重试等等。所以,也许你会写出下面这样的代码。

[csharp] view plaincopyprint?

    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,你会以一种很酷的方式来实现它:
[csharp] view plaincopyprint?

    [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操作”这些东西。它提供了可重用性和可维护性。最好的一点是它的“轻量级”——仅仅一个很小得类。
[csharp] view plaincopyprint?

    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是如何工作的:
[csharp] view plaincopyprint?

    [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调用,处理文件等等。而这样的“切面”无疑让我对处理这样的问题时轻松了许多。

下面展示了一下它是如何工作的,它创建了一个代理的组合。而结果就像如下这段代码:
[csharp] view plaincopyprint?

    Log(() =>
    {
        HowLong(() =>
        {
            Retry(() =>
            {
                Do(() =>
                {
                    CustomerData data = new CustomerData();
                    data.Insert(firstName, lastName, age, attributes);
                });
            });
        });
    });  

AspectF类除了压缩这样的代码之外,其他什么都没有。

下面展示,你怎样创建你自己的“切面”。首先为AspectF类创建一个扩展方法。比如说,我们创建一个Log:
[csharp] view plaincopyprint?

    [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方法调用。
[csharp] view plaincopyprint?

    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方法做最后的执行操作。
[csharp] view plaincopyprint?

    /// <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你自己的‘切面’)。

[csharp] view plaincopyprint?

    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形式的编程而无需使用任何“笨重”的框架。
时间: 2024-10-13 12:56:32

使用AOP 使C#代码更清晰 转yanghua_kobe的相关文章

使用AOP 使C#代码更清晰

简介 如果你很熟悉面向方面编程(AOP),你就会知道给代码增加"切面"可以使代码更清晰并且具有可维护性.但是AOP通常都依赖于第三方类库或者硬编码的.net特性来工作.虽然这些实现方式的好处大于它们的复杂程度,但是我仍然在寻找一种实现AOP的更为简单的方式,来试我的代码更为清晰.我将它们单独移出来,并命名为AspectF. Aspect Oriented Programming (AOP)的背景 "切面"指的是那些在你写的代码中在项目的不同部分且有相同共性的东西.它

友坚4412开发板怎样在source insight中使汇编代码高亮显示?

友坚4412开发板怎样在source insight中使汇编代码高亮显示?4412开发板 做ARM嵌入式开发时,有时得整汇编代码,但在SIS里建立PROJECT并ADD TREE的时候,根据默认设置并不会把该TREE里面所有汇编文件都包含进来,默认只加了.inc和.asm后缀的, .s后缀的没有.而且用SIS打开.s的文件时,一片黑白没有色彩, 感觉回到DOS的EDIT时代里了. 解决方法是在Options->Document Options里面,点左上的Document Type下拉菜单,选择

Operating System-Thread(5)弹出式线程&amp;&amp;使单线程代码多线程化会产生那些问题

本文主要内容 弹出式线程(Pop-up threads) 使单线程代码多线程化会产生那些问题 一.弹出式线程(Pop-up threads) 以在一个http到达之后一个Service的处理为例子来介绍弹出式线程. 上面的例子中传统的做法有可能是在Service中有一个线程一直在等待request的到达,等request到达后这个线程会开始检查请求最后在进行处理.当这个线程在处理request的时候,后面来的request会被block,一直到线程处理完当前request为止.如下图所示. 弹出

c#转Java ,如何折叠一段代码使整个代码看起来简洁

Java netBeans/Eclips 如何折叠一段代码使整个代码看起来简洁 最近刚用Java,以前写C#的时候,通过region操作可以使一段代码折叠起来,使整段程序缩成一行,看起来清爽了许多,现在用netBeans, 发现自带的代码折叠功能只能折叠一个整个的method,不能选择一段进行折叠,请问能实现类似于C#的功能吗?如 #region 很长的代码 #endregion NetBeans内Editor设置了类似的功能. 只需要在模块开始注释以//<editor-fold>开始, 在模

用AOP改善javascript代码

Aop又叫面向切面编程,用过spring的同学肯定对它非常熟悉,而在js中,AOP是一个被严重忽视的技术点,这篇就通过下面这几个小例子,来说说AOP在js中的妙用. 1, 防止window.onload被二次覆盖. 2,无侵入的统计代码. 3, 分离表单请求和校验. 4,给ajax请求动态添加参数. 5,职责链模式. 6, 组合代替继承. 先给出before和after这2个“切面”函数. 顾名思义,就是让一个函数在另一个函数之前或者之后执行,巧妙的是,这2个函数可以公用this和argumen

Eclipse - 安装Indent Guide,使JAVA代码具备编辑缩进线,和Delphi一样酷!!

Delphi里面默认就有代码编辑缩进线,这在编写逻辑复杂的大块代码时候非常有好处,可以清楚地看到每块代码的范围,下面就是Delphi里面代码编辑缩进线的截图: 非常棒的效果,我喜欢,在处理复杂逻辑代码时候,这个功能极其有用,这是我写的一个复杂方法的部分代码,还没看到全部呢,如果没有这种代码编辑缩进线,应该没有现在幸福!!! Eclipse默认没有这种功能,真期望它也能够具备这种功能,经过一番努力,终于做到了!! 我已经把IndentGuide插件处理完毕了,大家直接可以拿过来安装了! 处理好的插

spring:利用Spring AOP 使日志输入与方法分离

对方法进行日志输出是一种很常见的功能.传统的做法是把输出语句写在方法体的内部,在调用该方法时,用输入语句输出信息来记录方法的执行! 1.先写一个普通类: package com.importnew; public class Common { public void execute(String username,String password){ System.out.println("------------------执行 execute()方法----------------"

java 代码规范

1. 导读 昨天在技术交流群,有位读者吐槽刚刚接手的代码(文章标题),代码全篇无格式,无注释,多个嵌套不打括号,函数命名不规范,代码实在看不下去.随着软件项目代码的日积月累,系统维护成本变得越来越高,代码质量是所有软件团队面临的共同问题. 持续地优化代码,提高代码的质量,是提升系统生命力的有效手段之一.软件系统思维有句话“Less coding, more thinking(少编码.多思考)”,也有这么一句俚语“Think more, code less(思考越多,编码越少)”.所以,我们在编码

Android快速开发框架——AndroidAnnotations(Code Diet)

简介:AndroidAnnotations是一个依赖注入方式来简化代码结构 ,快速开发的开源框架,使结构代码更清晰,减少代码重复性.对今后我们做自动化测试和自动化埋点开发都会提高开发效率.跟我们之前使用android开发习惯有一定差异,下面我们来了解一下这个框架的使用和优势. 特点:(1)依赖注入:包括view,extras,系统服务,资源等等(2)简单的线程模型,通过annotation表示方法运行在ui线程还是后台线程(3)事件绑定:通过annotation表示view的响应事件,不用在写内