zoukankan      html  css  js  c++  java
  • 结构型模式---修饰器模式

    修饰器模式

    无论何时我们想对一个对象添加额外的功能,都有下面这些不同的可选方法。
        1.如果合理,可以直接将功能添加到对象所属的类(例如,添加一个新的方法)
        2.使用组合
        3.使用继承
    与继承相比,通常应该优先选择组合,因为继承使得代码更加难以复用,继承关系是静态的,并且应用于整个类以及这个类的所有实例。
    设计模式为我们提供第四种可选方法,以支持动态地(运行时)扩展一个对象的功能,这种方法就是修饰器(装饰器)。修饰器模式能够以透明的方式(不会影响其他对象)动态地将功能添加到一个对象中。
    在许多编程语言中,使用子类化(继承)来实现修饰器模式。在Python中,我们可以(并且应该)使用内置的修饰器特性。一个Python修饰器就是对Python语法的一个特定改变,用于扩展一个类、方法或函数的行为,而无需使用继承。
    从实现的角度来说,Python修饰器是一个可调用对象(函数,方法或类),接受一个函数fin作为输入,并返回另一个函数对象fout。这意味着可以将任何具有这些属性的可调用对象当做一个修饰器。
    注意:修饰器模式与Python修饰器之间并不是一对一的等价关系。Python修饰器能做的实际上比修饰器模式多得多,其中之一就是实现修饰器模式。
    现实生活中的例子:修饰器模式用于扩展一个对象的功能。这类扩展的实际例子有,给枪加一个消音器、使用不同的照相机镜头等。
    应用案例:当用于实现横切关注点时,修饰器模式会大显神威。一般来说,应用中有些部件是通用的,可应用于其他部件,这样的部件被看作横切关注点。
    #我们知道,使用递归算法实现斐波那契数列,直接了当,但性能问题较大。
    #如下:
    def fib(n):
        assert (n>=0),'n must be >=0'
        return n if n in (0,1) else fib(n-1)+fib(n-2)
    
    if __name__ == '__main__':
        from timeit import Timer
        t = Timer('fib(8)','from __main__ import fib')
        print(t.timeit())
    
    #耗时 9.770086538557482 s
    #使用memoization的方法试着改善
    known = {0:0,1:1}
    
    def fib(n):
        assert (n>=0),'n must be >=0'
        if n in known:
            return known[n]
        res =  fib(n-1)+fib(n-2)
        known[n]=res
        return res
    
    if __name__ == '__main__':
        from timeit import Timer
        t = Timer('fib(100)','from __main__ import fib')
        print(t.timeit())
        
    #耗时 0.16005969749279084 s
    #执行基于memorization的代码实现,可恶意看到性能得到了很大的提升,甚至对于计算大的数值也是可以接受的。但这方法有一个问题,虽然性能不再是一个问题,但是代码却没有不使用memorization时那么简洁。
    #如果我们想要扩展代码,加入更多的数学函数,将其转变成一个模块,那又会是什么样的的呢?假设决定加入的下一个函数是nsum(),该函数返回前n个数字的和。
    #使用memeorization实现nsum()函数的代码如下:
    known_sum = {0:0}
    
    def nsum(n):
        assert (n>0), 'n must be >= 0'
        if n in known_sum:
            return known_sum[n]
        res = n + nsum(n-1)
        known_sum[n] = res
        return res
    我们发现新增一个函数多了一个名为known_sum的新字典,为nsum提供缓存作用,并且函数本身也不比使用memorization时的更复杂。
    这个模块逐步变得不必要的复杂。操持函数与朴素版本一样的简单,但在性能上又能与使用memorization的函数接近,这可能吗?
    幸运的是确实可能,解决方案就是使用修饰器模式。
    #创建一个memorize()函数,其接受一个函数fn作为输入,使用名为know的字典作为缓存。如下:
    import functools
    def memoize(fn):
        known = {}
        @functools.wraps(fn)
        def memoizer(*args):
            if args not in known:
                known[args] = fn(*args)
            return known[args]
        return memoizer
    #现在对朴素版本应用memoize()修饰器。这样既能保持代码的可读性又不影响性能。我们通过修饰来应用一个修饰器。修饰使用@name语法,其中name是指我们想要使用的修饰器名称。
    import functools
    def memoize(fn):
        known = {}
        @functools.wraps(fn)
        def memoizer(*args):
            if args not in known:
                known[args] = fn(*args)
            return known[args]
        return memoizer
    
    @memoize
    def fib(n):
        assert (n>=0),'n must be >=0'
        return n if n in (0,1) else fib(n-1)+fib(n-2)
    
    @memoize
    def nsum(n):
        assert (n>=0),'n must be >=0'
        return 0 if n==0 else n+nsum(n-1)
    
    if __name__ == '__main__':
        from timeit import Timer
        measure = [{'exec':'fib(100)','import':'fib','func':fib},{'exec':'nsum(200)','import':'nsum','func':nsum}]
        for m in measure:
            t = Timer('{}'.format(m['exec']),'from __main__ import {}'.format(m['import']))
            print('name:{},doc:{},executing:{},time:{}'.format(m['func'].__name__,m['func'].__doc__,m['exec'],t.timeit()))
    
    #name:fib,doc:None,executing:fib(100),time:0.18119357924100937
    #name:nsum,doc:None,executing:nsum(200),time:0.1972677136059823

    小结

    我们使用修饰器模式来扩展一个对象的行为,无需使用继承,非常方便。修饰器模式是实现横切关注点的绝佳方案,因为横切关注点通用但不太适合使用面向对象编程范式来实现。
    做一枚奔跑的老少年!
  • 相关阅读:
    正则表达式和grep
    Java程序员常用工具集
    在 TDA 工具里看到 Java Thread State 的第一反应是
    Java线程Dump分析工具--jstack
    一个Tomcat配置参数引发的血案
    JVM性能调优监控工具jps、jstack、jmap、jhat、jstat等使用详解
    jstat的用法
    Java命令学习系列(2):Jstack
    JVM调优之jstack找出最耗cpu的线程并定位代码
    linux内核cdev_init系列函数(字符设备的注册)
  • 原文地址:https://www.cnblogs.com/xiaoshayu520ly/p/10983861.html
Copyright © 2011-2022 走看看