先給出一個四人團(tuán)對Decorator mode的定義:動態(tài)地給一個對象添加一些額外的職責(zé)。 再來說說這個模式的好處:認(rèn)證,權(quán)限檢查,記日志,檢查參數(shù),加鎖,等等等等,這些功能和系統(tǒng)業(yè)務(wù)無關(guān),但又是系統(tǒng)所必須的,說的更明白一點(diǎn),就是面向方面的編程(AOP)。AOP把與業(yè)務(wù)無關(guān)的代碼十分干凈的從系統(tǒng)中切割出來,但是Decorator mode的強(qiáng)大遠(yuǎn)不止于此,本文的重點(diǎn)在于Decorator mode在Python中的應(yīng)用,所以就不再過多描述Decorator mode本身了,要想深入了解該模式,請參考四人團(tuán)的經(jīng)典之作《設(shè)計(jì)模式》。 在Python中Decorator mode可以按照像其它編程語言如C++, Java等的樣子來實(shí)現(xiàn),但是Python在應(yīng)用裝飾概念方面的能力上遠(yuǎn)不止于此,Python提供了一個語法和一個編程特性來加強(qiáng)這方面的功能。Python提供的語法就是裝飾器語法(decorator),如下: @aoo def foo(): pass def aoo(fn): return fn 這里不對裝飾器語法做過多的解釋,因?yàn)檠b飾器語法也是基于我將要介紹的另一個編程特性,當(dāng)我介紹完另一個編程特性后,相信你會對裝飾器語法有更深入的認(rèn)識。 一個十分重要的編程特性“閉包”(closure)隆重登場(題外話:據(jù)說“閉包”已經(jīng)進(jìn)入java下一版的特性候選列表了) 在Python,PHP,Perl,Ruby,JavaScript等動態(tài)語言中,都已經(jīng)實(shí)現(xiàn)了閉包特性,為什么這個特性那么重要呢?我們先來看看它的通俗一些的定義: OO編程范式中的對象是“整合了函數(shù)的數(shù)據(jù)對象”,那么閉包就是“整合了數(shù)據(jù)的函數(shù)對象” 源文檔 <http://blog.csdn.net/muzisoft2008/archive/2009/03/30/4036744.aspx> 借用一個非常好的說法來做個總結(jié)(注4):對象是附有行為的數(shù)據(jù),而閉包是附有數(shù)據(jù)的行為。 源文檔 <http://www.ibm.com/developerworks/cn/linux/l-cn-closure/index.html> 這篇文章更詳細(xì)的介紹了閉包 閉包的概念、形式與應(yīng)用 源文檔 <http://www.ibm.com/developerworks/cn/linux/l-cn-closure/index.html> 下面左邊的foo1只是一個普通的內(nèi)嵌函數(shù),而右邊的boo則是一個閉包,
boo的特殊性在于引用了外部變量b,當(dāng)aoo返回后,只要返回值(boo)一直存在,則對b的引用就會一直存在。 上面的知識可能需要花些時間消化,如果你覺得已經(jīng)掌握了這些知識,下面就回歸正題,看看這些語言特性是怎樣來實(shí)現(xiàn)Python中裝飾的概念的。 還是讓我們先看一個簡單的例子,然后逐步深入。這個例子就是加鎖,怎樣實(shí)現(xiàn)加鎖的功能? 具體需求是這樣的:我有一個對象,實(shí)現(xiàn)了某些功能并提供了一些接口供其它模塊調(diào)用,這個對象是運(yùn)行在并發(fā)的環(huán)境中的,因此我需要對接口的調(diào)用進(jìn)行同步,第一版的代碼如下: class Foo(object): def __init__(self, …): self.lock = threading.Lock() def interface1(self, …): self.lock.acquire() try: do something finally: self.lock.release() def interface2(self, …): same as interface1() … 這版代碼的問題很明顯,那就是每個接口函數(shù)都有相同的加鎖/解鎖代碼,重復(fù)的代碼帶來的是更多的鍵入,更多的閱讀,更多的維護(hù),以及更多的修改,最主要的是,程序員本應(yīng)集中在業(yè)務(wù)上的的精力被分散了,而且請注意,真正的業(yè)務(wù)代碼在距離函數(shù)定義2次縮進(jìn)處開始,即使你的顯示器是寬屏,這也會帶來一些閱讀上的困難。 你直覺的認(rèn)為,可以把這些代碼收進(jìn)一個函數(shù)中,以達(dá)到復(fù)用的目的,但是請注意,這些代碼不是一個完整同一的代碼塊,而是在中間嵌入了業(yè)務(wù)代碼。 現(xiàn)在我們用裝飾器語法來改進(jìn)這部分代碼,得到第2版代碼: def sync(func): def wrapper(*args, **kv): self = args[0] self.lock.acquire() try: return func(*args, **kv) finally: self.lock.release() return wrapper class Foo(object): def __init__(self, …): self.lock = threading.Lock() @sync def interface1(self, …): do something @sync def interface2(self, …): do something … 一個裝飾器函數(shù)的第一個參數(shù)是所要裝飾的那個函數(shù)對象,而且裝飾器函數(shù)必須返回一個函數(shù)對象。如sync函數(shù),當(dāng)其裝飾interface1時,參數(shù)func的值就是interface1,返回值是wrapper,但類Foo實(shí)例的interface1被調(diào)用時,實(shí)際調(diào)用的是wrapper函數(shù),在wrapper函數(shù)體中間接調(diào)用實(shí)際的interface1;當(dāng)interface2被調(diào)用時,也調(diào)用的是wrapper函數(shù),不過由于在裝飾時func已經(jīng)變成interface2,所以會間接地調(diào)用到實(shí)際的interface2函數(shù)。 使用裝飾器語法的好處:
缺點(diǎn):
我們可以更進(jìn)一步想一想:
為了解決上述的缺點(diǎn),第3版代碼如下: class DecorateClass(object): def decorate(self): for name, fn in self.iter(): if not self.filter(name, fn): continue self.operate(name, fn) class LockerDecorator(DecorateClass): def __init__(self, obj, lock = threading.RLock()): self.obj = obj self.lock = lock def iter(self): return [(name, getattr(self.obj, name)) for name in dir(self.obj)] def filter(self, name, fn): if not name.startswith('_') and callable(fn): return True else: return False def operate(self, name, fn): def locker(*args, **kv): self.lock.acquire() try: return fn(*args, **kv) finally: self.lock.release() setattr(self.obj, name, locker) class Foo(object): def __init__(self, …): … LockerDecorator(self).decorate() def interface1(self, …): do something def interface2(self, …): do something … 對對象的功能裝飾是一個更一般的功能,不僅限于為接口加鎖,我用2個類來完成這一功能,DecorateClass是一個基類,只定義了遍歷并應(yīng)用裝飾功能的算法代碼(template method),LockerDecorator實(shí)現(xiàn)了為對象加鎖的功能,其中iter是迭代器,定義了怎樣遍歷對象中的成員(包括數(shù)據(jù)成員和成員函數(shù)),filter是過濾器,定義了符合什么規(guī)則的成員才能成為一個接口,operate是執(zhí)行函數(shù),具體實(shí)施了為對象接口加鎖的功能。 而在業(yè)務(wù)類Foo的__init__函數(shù)中,只需要在最后添加一行代碼:LockerDecorator(self).decorate(),就可以完成為對象加鎖的功能。 如果你的對象提供的接口有特殊性,完全可以通過直接改寫filter或者繼承LockerDecorator并覆蓋filter的方式來實(shí)現(xiàn);此外,如果要使用其他的裝飾功能,可以寫一個繼承自DecorateClass的類,并實(shí)現(xiàn)iter,filter和operate三個函數(shù)即可。 |
|