posts - 43,  comments - 394,  trackbacks - 18
公告

一. 从Python说起

    Python是一门强大的语言,它包含了很多神奇的技巧,作为一门动态语言,天生的优势使得很多特性让静态语言难以达到。今天我们展示的就是Python中一个很有用特性:“Decorator”,中文可以译作“装饰器”,那么,Decorator是什么?

    在Dr.Dobb’s的文章中有这样一段描述“Decorators are Python objects that can register,annotate,and/or wrap a Python function or object.”。

    具体来说,Decorator就是一个对函数的封装,它可以让你不改变函数本身的情况下对函数的执行进行干预,比如在执行前进行权限认证,日志记录,甚至修改传入参数,或者在执行后对返回结果进行预处理,甚至可以截断函数的执行等等。

    看到定义是不是有一定的熟悉感,没错,本质上来说,它就是我们常说的面向方面编程(Aspect-Oriented Programming),简称AOP,相信大家对AOP也是如雷贯耳了,由于动态语言本身上的优势,使得这类技术在动态语言平台上是如鱼得水。

二. Decorator在Python下的例子

    看了很多很玄乎的概念,下面用一个简单的Python例子来具体说明如何进行Decorator编程。

def logger(name):
    def loggerWrapper(fun):
        def logging(self):
            print "User is %s." % name
            print "Start Logging"
            result = fun(self)
            print "End Logging."
            return result
        return logging
    return loggerWrapper

def debugger(name):
    def debuggerWrapper(fun):
        def debugging(self):
            print "Debug %s" % name
            print "Start Debug"
            result = fun(self)
            print "End Debug"
            return result
        return debugging
    return debuggerWrapper

class MyClass():
    @logger("Leven")
    @debugger("test")
    def Test(self):
        print("function MyClass::Test called.")
        return "I am Reuslt."
        

if __name__ == "__main__":
    mc = MyClass()
    print "Result:%s" % mc.Test()

 

    执行改程序,可以得到如下的结果:

001

    回过头看看程序的特点,从源码可以了解到,Decorator的使用很简单,直接放置在函数定义前即可,通过@xxx的方式放置,系统就可以识别。下面我们研究下Decorator的一些特点。

  1. Decorator的本质是一个函数,它可以有传入的参数,它需要返回一个函数对象(注意这儿两个函数的区别),因此,在程序中,名为logger的Decorator返回了一个名为loggerWrapper的函数对象,同理,名为debugger的Decorator返回了一个名为debuggerWrapper的函数对象,这是Decorator定义的第一要点:Decorator函数一定要返回一个函数对象。
  2. Decorator返回的函数对象也是有要求的,因为这个返回的函数对象是系统在调用该函数的时候执行的,因此,系统调用该函数对象的时候会传入当前被装饰的函数对象(注意,这里可能并不是原始定义的函数对象),同时需要返回一个和被修饰的函数定义一致的函数供系统调用。对比代码中的定义,loggerWrapper函数接收一个fun的参数,很显然,该参数就是当前被修饰的函数对象,同时,它返回logging函数对象,该函数的定义和被修饰函数完全保持一致。
  3. 系统在调用被修饰方法的时候,实际上是执行了第2点说明中返回的函数对象。也就是说,系统会对函数“偷梁换柱”,实际执行的并不是原来定义的函数体。至于原来定义的函数体是否会执行,那就要看Decorator的具体实现了。
  4. 对于多个Decorator的情况,系统会顺序的进行前两步的动作,同时,系统对Decorator的处理是倒序的。

    下面我们再次顺序的对上面例子的执行方式进行说明:

    首先,系统检查到函数Test有Decorator,因为是倒序处理,所以,首先针对debugger,系统会直接执行debuggerWrapper,并将Test作为参数fun传入,debuggerWrapper函数执行完毕之后返回了debugging函数对象,于是,系统将Test偷换成debugging,因此,如果执行Test方法,实际执行的debugging方法,继续,还有一个装饰器logger,同样的,系统会直接执行loggerWrapper函数,并将当前的函数(注意,当前的函数不再是Test了,因为经过debuger的装饰,Test已经被debugging所替代,因此,这儿传入的是debugging函数对象)作为参数fun传入,loggerWrapper方法执行之后会返回logging函数对象,该函数定义和Test完全一致,因此,系统又将该位置的debugging函数(Test在先前被debugging替换了)替换成logging函数,因此,该函数在整体执行的时候是直接跑去logging函数执行的。

    在上面的例子中,系统调用Test函数实际执行logging函数,logging函数中调用fun(self)实际调用的是debugging(self),而debugging中调用的fun(self)才是真正的执行了Test方法。

三. 参考C#进行思考

    看到如此简单实用的Decorator,是不是心动了呢?想办法也让C#能享受到这个好处吧。在C#中,Attribute的表现形式和Python很是相似,因此,我们考虑使用Attribute和一系列相关的操作来达到模拟Python的执行过程。

    在C#中,函数对象由委托来进行描述,因此,我们考虑,同样的,让支持Decorator操作的Attribute实现一个特定的接口,该接口返回一个委托,然后,将实际执行该方法的时候去执行该委托,那么考虑以下形式:

public class MyClass{
    [Logger(“Leven”)]
    public string Test(string s) {
        xxx
        return xxx;
    }
}

public delegate string TestMethodDelegate(string s);

public class LoggerAttribute : Attribute {
    public string Name { get; private set; }

    public LoggerAttribute(string name) {
        Name = name;
    }

    TestMethodDelegate LoggerWrapper(TestMethodDelegate fun) {
        return e => {
            Console.WriteLine(“User is {0}.”, Name);
            Console.WriteLine(“Start Logging”);
            var result = Fun(e);
            Console.WriteLine(“End Logging”);
            return result;
        }
    }
}

    这样一来形式就和Python的Decorator大致一样了,然而,在正常情况下,C#是不会像Python一样自动处理LoggerAttribute和Test的关系的,因此,LoggerWrapper永远也没法执行,更不用说替换Test方法了,于是,下面我们需要继续考虑具体功能的实现。

四. 考虑功能的实现

    要实现Decorator,很重要的一点就是能做到对方法“偷梁换柱”,而C#在运行期是不能修改方法的。当然,Decorator本质上是一种AOP的表现形式,因此,我们大可考虑AOP常见的实现方式。AOP一般有动态代理和静态织入两种实现方式,动态代理虽然有一些小小的限制,但是相比静态织入,实现和使用上都大大简单化,因此,我们考虑使用动态代理的方式实现该功能。

    对于MyClass类,我们希望能生成一个新的MyClassWrapper类,该类继承自MyClass,同时,我们要求将Test方法修改成virtual方法,这样,我们可以在MyClassWrapper类中对Test方法进行改写,这样以来就达到了对方法“偷梁换柱”的做法,虽然比起Python不够利索,但是能满足我们的要求也是大大的欢迎。那么,我们的MyClassWrapper类要求如下定义:

public class MyClassWrapper : MyClass {
    public override string Test() {
        LoggerAttribute attribute = 
typeof(MyClass).GetMethod(“Test”).GetCustomAttribute(typeof(LoggerAttribute), true)[0] as LoggerAttribute; //获取基类中的第一个LoggerAttribute
        //调用LoggerWrapper方法,获取新的委托.
        var fun = attribute.LoggerWrapper(new TestMethodDelegate(Test)); 
        return fun();
    }
}

    这样一来,我们就改写了Test方法的实现,达到和Python中的Decorator一致的效果。然而,C#的这种处理方式还是和Python有不同的,在Python中,是Decorator函数替换掉被装饰函数,但是在C#中,因为方法不能被另一个委托对象替换,我们只能在方法内部手动执行装饰过的方法,不过,效果是完全一致的。当然,上面的例子只是代表了一种实现的方法,在多个Attribute的情况下还需要很多不同的处理,不过,这样就代表在C#上实现python的Decorator是完全行得通的。

    在上面的例子中,我们针对Test方法做到了装饰,但是,作为一个框架,我们要求能对所有满足要求的方法都能实现装饰,因此,我们需要对上面的实现进行修改。很重要的一点,我们上面的装饰器仅仅能处理string Test(string)这样的方法,如果是string Test(object)我们则需要重新定义,使用可是大大的不便,于是,考虑用一种通用的方法签名来代表所有的方法调用,也就是使用Func<object, object[], object>来代表任意方法。

五. 本篇小结

    在本篇中,我们将Python中Decorator的原理进行了透彻的分析,同时评估了在C#实现同样的Decorator的可行性,通过分析可以肯定,在C#上,通过一定的技术手段,我们完全可以实现和Python类似的Decorator功能。

    在下一篇中,文章将从具体实践出发,对Python的Decorator进行一个完整的移植,因此,下一篇将暂定名为“编码篇”。

posted on 2009-12-28 12:22 Leven 阅读(...) 评论(...) 编辑 收藏
CopyRight 2008, Leven's Blog xhtml | css
Leven的个人Blog