目录
定义:
修饰模式,是面向对象编程领域中,一种动态地往一个类中添加新的行为的设计模式。就功能而言,修饰模式相比生成子类更为灵活,这样可以给某个对象而不是整个类添加一些功能。 通过使用修饰模式,可以在运行时扩充一个类的功能。原理是:增加一个修饰类包裹原来的类,包裹的方式一般是通过在将原来的对象作为修饰类的构造函数的参数。装饰类实现新的功能,但是,在不需要用到新功能的地方,它可以直接调用原来的类中的方法。修饰类必须和原来的类有相同的接口。
装饰器(Decorator)模式是我使用率很高的一种模式,一旦熟悉了, 你就会爱上它.
装饰器使用前:
让我们从一个具体的问题开始:
# 编写一个函数,获取一个URL的内容
import urllib
def fetch(url):
return urllib.urlopen(url).read()
上面这段代码很简单,获取一个URL的内容。但这时我们遇到了一个问题,由于网络状况或者网站的负载等原因,有些情况下访问会失败,但是经过一些重试之后就可以成功。这个时候,我们就需要把代码做一些修改,见下面的代码:
import urllib
import time
def fetch(url):
for _ in xrange(5):
try:
return urllib.urlopen(url).read()
except:
time.sleep(1)
else:
raise RuntimeError
上面代码能解决我们之前提到的网络暂时问题, 但同时引入另外问题, 多出来的代码和fetch本身无关, 而增加的重试代码其实在很多地方都是通用的, 而它确变成这个fetch私有的代码. 好了,我们的装饰器可以派上用场了.
装饰器定义:
装饰器是一个函数,它的使用方法是在被装饰的函数前加上@<decorator>
,从原理上说,装饰器函数是一个接收函数作为输入参数,返回一个新的函数的函数。见如下例子:
def decorator(f): # 输入参数为函数
def wrapper(*args, **kwargs): # wrapper函数,用于替代f
print 'start in function wrapper.'
result = f(*args, **kwargs)
print 'end in function wrapper.'
return result
return wrapper # 返回wrapper函数
# 被装饰函数
def test(name):
print "in test function,", name
# 不用装饰器语法
wrapper_test = decorator(test)
wrapper_test('hello')
#输出为:
# start in function wrapper.
# in test function,hello
# end in function wrapper.
我们在上面定义了一个装饰器函数decorator,一个被装饰函数test,当我们需要为test函数增加功能的时候,通过decorator(test)生成一个新的函数来实现。可以看到,这样的代码有点罗嗦,如果test有多个装饰器,这里会生成多个类似wrapper_test的新函数,所以我们渴望有一个好用的语法糖,而Python提供了。
上面的代码我们可以变成如下:
def decorator(f): # 输入参数为函数
def wrapper(*args, **kwargs): # wrapper函数,用于替代f
print 'start in function wrapper.'
result = f(*args, **kwargs)
print 'end in function wrapper.'
return result
return wrapper # 返回wrapper函数
# 被装饰函数
def test(name):
print "in test function,", name
# 用装饰器语法
@decorator
test('hello')
#输出为:
# start in function wrapper.
# in test function,hello
# end in function wrapper.
可以代码对比可以看到通过@decorator装饰后的代码与之前效果一样,但是语法简洁了很多。
使用装饰器重构上面retry的代码
import urllib
import time
import functools
def retry(times, interval):
def _retry(f):
@functools.wraps(f)
def wrapper(*args, **kwds):
for _ in xrange(times):
try:
return f(*args, **kwds)
except:
time.sleep(interval)
else:
raise RuntimeError
return wrapper
return _retry
@retry(5, 1)
def fetch(url):
return urllib.urlopen(url).read()
这时候,fetch基本又回到了最初的样子,简单明确,而retry作为一个独立的函数则可以被很多其他地方复用,我们成功地把两者解藕了
常用使用case:
这就是装饰器模式,在很多地方都有它的应用,比如最常见的property。
class MyObj(object):
@property
def name(self):
return 'MyObj', self.__hash__()
在一些library如bottle里面也大量使用了装饰器,如:
from bottle import route, run, template
@route('/hello/<name>')
def index(name):
return template('<b>Hello </b>!', name=name)
run(host='localhost', port=8080)
从名字就可以看出route是一个路由装饰器,它的实现如下:
def make_default_app_wrapper(name):
''' Return a callable that relays calls to the current default app. '''
@functools.wraps(getattr(Bottle, name))
def wrapper(*a, **ka):
return getattr(app(), name)(*a, **ka)
return wrapper
route = make_default_app_wrapper('route')
总结:
装饰器模式是让应用解藕的一个非常好用的模式,对于认证、缓存、重试等需求,用该模式可以在不改变现有代码逻辑的情况下添加增强功能。
但是,也需要注意的是,不是什么代码都适合放在装饰器里面的,如果那本来就是函数逻辑的一部分,那还是放在函数内部吧,另外在做单元测试的时候,我们通常也会把装饰器都mock掉,以方便测试。装饰器这种场景如何mock? 我会在UT部分阐述.