装饰器模式(Decorator Pattern):在不修改原对象结构的前提下,实现扩展对象功能。
通常给一个对象添加新功能有三种方式: - 直接给对象所属的类添加方法。 - 使用『组合』 - 使用『继承』,优先使用组合而非继承。 装饰器模式提供了第四种选择,通过动态改变对象扩展对象功能。其他编程语言通常使用继承实现装饰器装饰器模式,而python内置了装饰器。装饰器有很多用途,比如数据校验,事务处理,缓存,日志等。比如用装饰器实现一个简单的缓存,python3.5自带了functools.lru_cache
1 介绍
装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
何时使用:在不想增加很多子类的情况下扩展类。
2 适用场景
装饰器模式主要用于给当前类/方法/函数动态增加功能,也叫做添加横切面,常用于以下目的:
- 数据校验
- 事务处理(这里的事务类似于数据库事务,意味着要么所有步骤都成功完成,要么事务失败)
- 缓存
- 日志
- 监控
- 调试
- 业务规则
- 压缩
- 加密
一般来说,应用中有些部件是通用的,可应用于其他部件,这样的部件被看作横切关注点。
使用修饰器模式的另一个常见例子是图形用户界面(Graphical User Interface,GUI)工具集。在一个GUI工具集中,我们希望能够将一些特性,比如边框、阴影、颜色以及滚屏,添加到单个组件/部件。
3 实现步骤
装饰器模式主要步骤在于根据需求定义修饰器,取决于被修饰对象。根据对象的不同,修饰器有很多种实现方式。
步骤一:定义修饰器
步骤二:使用修饰器修饰函数/方法/类
示例代码:
from functools import wraps
# 步骤一:定义修饰器
def memory_cache(fn):
cached = dict()
@wraps(fn)
def inner_func(*args):
key = str(args)
if key in cached:
resp = cached[key]
else:
resp = fn(*args)
cached[key] = resp
return resp
return inner_func
# 给函数添加修饰
@memory_cache
def fibonacci(n):
assert (n >= 0), 'n must be >= 0'
return n if n in (0, 1) else fibonacci(n - 1) + fibonacci(n - 2)
if __name__ == '__main__':
from timeit import Timer
t = Timer('fibonacci(8)', 'from __main__ import fibonacci')
print(t.timeit())
代码输出:
0.7204972780309618
4 代码实践
案例一:给函数添加修饰器,统计函数执行时间,并打印相关日志
import time
import logging
from functools import wraps
logger = logging.getLogger("demo")
"""
步骤一:定义修饰器
"""
def timeit(func):
@wraps(func)
def inner_wrapper(self, *args, **kwargs):
time_start = time.time()
resp = func(self, *args, **kwargs)
time_end = time.time()
logger.warning(' '.join([func.__name__, "time cost:", str(time_end - time_start)]))
return resp
return inner_wrapper
"""
步骤二:应用修饰器
"""
class MathDemo(object):
@timeit
def sum_data(self, n):
i = n
data = 1
while i > 0:
data = data * i
i = i - 1
return data
if __name__ == '__main__':
demo = MathDemo()
res = demo.sum_data(4)
print(res)
代码输出:
sum_data time cost: 4.0531158447265625e-06
24
案例二:使用修饰器扩展类的功能
通过反省或者重写类定义的某部分来修改它的行为,但是又不希望使用继承或元类的方式.
这种情况可能是类装饰器最好的使用场景了。下面是一个重写了特殊方法 __getattribute__ 的类装饰器, 可以打印日志
def log_getattribute(cls):
#获取类的属性
orig_attr = cls.__getattribute__
def new_attr(self, name):
print("add new attribute: {}".format(name))
return orig_attr(self, name)
cls.__getattribute__ = new_attr
return cls
@log_getattribute
class A:
def __init__(self,x):
self.x = x
def spam(self):
pass
a = A("12")
a.spam()
代码输出:
add new attribute: spam
更多实践,请参考前面介绍修饰器的博客:
https://blog.youkuaiyun.com/biheyu828/article/details/82532126
5 软件例子
Django框架大量地使用修饰器,其中一个例子是视图修饰器。Django的视图(View)修饰器 可用于以下几种用途(请参考网页[t.cn/RqrlJbA])。
- 限制某些HTTP请求对视图的访问控制特定视图上的缓存行为
- 按单个视图控制压缩
- 基于特定HTTP请求头控制缓存
Grok框架也使用修饰器来实现不同的目标,比如下面几种情况。
- 将一个函数注册为事件订阅者
- 以特定权限保护一个方法
- 实现适配器模式
参考文献:
https://www.jianshu.com/p/013985a58841