結構型模式---修飾器模式

修飾器模式算法

不管什麼時候咱們想對一個對象添加額外的功能,都有下面這些不一樣的可選方法。
    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

小結編程

咱們使用修飾器模式來擴展一個對象的行爲,無需使用繼承,很是方便。修飾器模式是實現橫切關注點的絕佳方案,由於橫切關注點通用但不太適合使用面向對象編程範式來實現。
相關文章
相關標籤/搜索