23、设计模式:装饰器模式:动态扩展功能,Python装饰器与类装饰器,实战案例:权限校验
装饰器模式,说白了就是“给对象穿衣服”。
你有一件白T恤,想让它更酷一点,可以在外面套一件夹克,再冷一点还可以加一件风衣。你并没有改变T恤本身,只是动态地给它增加了功能。这就是装饰器模式的核心思想——在不修改原有代码的情况下,动态地扩展对象的功能。
我在项目中遇到过很多次这样的场景:一个函数写好了,突然要加日志、加权限校验、加性能统计。如果每个需求都去改函数内部代码,那代码很快就会变得一团糟。装饰器模式就是解决这个问题的利器。
什么是装饰器模式?
装饰器模式属于结构型设计模式。它允许你通过将对象放入包含行为的特殊封装器对象中来为原对象绑定新的行为。
用更直白的话说:装饰器是一个包装器,它在不改变原对象接口的前提下,给原对象添加额外的职责。
核心要点:
- 装饰器和被装饰对象实现相同的接口
- 装饰器持有被装饰对象的引用
- 装饰器可以在调用被装饰对象前后执行额外逻辑
Python中的函数装饰器
Python对装饰器模式有天然的语言级支持。你写的@decorator语法糖,本质上就是函数式编程的体现。
先看一个最简单的例子:
def my_decorator(func):
def wrapper():
print("调用函数之前")
func()
print("调用函数之后")
return wrapper
@my_decorator
def say_hello():
print("你好!")
say_hello()
输出结果:
调用函数之前
你好!
调用函数之后
嗯,这里要注意:@my_decorator 等价于 say_hello = my_decorator(say_hello)。说白了就是把原函数作为参数传给装饰器,然后返回一个新的函数替换掉原来的。
带参数的装饰器
有时候我们需要给装饰器传递参数,比如指定日志级别。这时候就需要再包一层:
def repeat(n):
def decorator(func):
def wrapper(*args, **kwargs):
for _ in range(n):
result = func(*args, **kwargs)
return result
return wrapper
return decorator
@repeat(3)
def greet(name):
print(f"你好,{name}")
greet("小明")
输出:
你好,小明
你好,小明
你好,小明
我个人习惯把这种三层嵌套的结构叫做“装饰器工厂”。外层函数接收参数,中间层接收函数,内层处理实际逻辑。
类装饰器
除了函数装饰器,Python还支持类装饰器。类装饰器通过实现__call__方法,让类的实例可以像函数一样被调用。
class TimerDecorator:
def __init__(self, func):
self.func = func
def __call__(self, *args, **kwargs):
import time
start = time.time()
result = self.func(*args, **kwargs)
end = time.time()
print(f"函数 {self.func.__name__} 执行耗时:{end - start:.4f}秒")
return result
@TimerDecorator
def slow_function():
import time
time.sleep(1)
print("执行完毕")
slow_function()
类装饰器的好处是:你可以保存状态。比如记录函数被调用了多少次,或者缓存一些计算结果。
小技巧:如果你需要装饰器保存状态,用类装饰器比用闭包更清晰。我曾经在做一个缓存系统时,就是用类装饰器实现的,状态管理起来非常方便。
实战案例:权限校验
好了,理论说完了。我们来看一个实际项目中经常遇到的场景——权限校验。
假设你有一个用户管理系统,不同的用户角色有不同的操作权限。你不想在每个函数里都写一遍权限判断逻辑,这时候装饰器就派上用场了。
from functools import wraps
# 模拟用户数据库
users_db = {
"admin": {"role": "admin", "name": "管理员"},
"editor": {"role": "editor", "name": "编辑"},
"viewer": {"role": "viewer", "name": "访客"},
}
# 权限装饰器
def require_permission(required_role):
def decorator(func):
@wraps(func)
def wrapper(user_id, *args, **kwargs):
user = users_db.get(user_id)
if not user:
print("用户不存在")
return None
# 角色权限等级
role_level = {
"admin": 3,
"editor": 2,
"viewer": 1,
}
user_level = role_level.get(user["role"], 0)
required_level = role_level.get(required_role, 0)
if user_level < required_level:
print(f"权限不足!{user['name']} 需要 {required_role} 权限")
return None
print(f"权限校验通过,欢迎 {user['name']}")
return func(user_id, *args, **kwargs)
return wrapper
return decorator
# 业务函数
@require_permission("editor")
def edit_article(user_id, article_id):
print(f"正在编辑文章 {article_id}")
@require_permission("admin")
def delete_article(user_id, article_id):
print(f"正在删除文章 {article_id}")
@require_permission("viewer")
def view_article(user_id, article_id):
print(f"正在查看文章 {article_id}")
# 测试
print("=== 管理员操作 ===")
edit_article("admin", 101)
delete_article("admin", 101)
view_article("admin", 101)
print("\n=== 编辑操作 ===")
edit_article("editor", 102)
delete_article("editor", 102) # 应该失败
view_article("editor", 102)
print("\n=== 访客操作 ===")
edit_article("viewer", 103) # 应该失败
view_article("viewer", 103)
输出结果:
=== 管理员操作 ===
权限校验通过,欢迎 管理员
正在编辑文章 101
权限校验通过,欢迎 管理员
正在删除文章 101
权限校验通过,欢迎 管理员
正在查看文章 101
=== 编辑操作 ===
权限校验通过,欢迎 编辑
正在编辑文章 102
权限不足!编辑 需要 admin 权限
权限校验通过,欢迎 编辑
正在查看文章 102
=== 访客操作 ===
权限不足!访客 需要 editor 权限
权限校验通过,欢迎 访客
正在查看文章 103
你看,通过一个@require_permission("editor")装饰器,我们就把权限校验逻辑从业务代码中剥离出来了。每个函数只需要关注自己的业务逻辑,权限的事情交给装饰器去处理。
避坑指南:我曾经在项目中使用装饰器时犯过一个错误——忘记使用@wraps。这会导致被装饰函数的元信息(如__name__、__doc__)丢失,调试时非常痛苦。所以,写装饰器时一定要加上@wraps(func)。
装饰器模式的知识体系
下面这张图帮你理清装饰器模式的核心脉络:
装饰器 vs 继承
你可能会问:用继承也能扩展功能,为什么还要用装饰器?
问得好。我们来看一个对比:
| 对比维度 | 装饰器模式 | 继承 |
|---|---|---|
| 扩展方式 | 动态组合 | 静态继承 |
| 灵活性 | 高,运行时决定 | 低,编译时决定 |
| 类爆炸 | 不会,一个装饰器可复用 | 容易,每加一个功能就要新建子类 |
| 适用场景 | 需要动态添加/撤销功能 | 功能层次结构稳定 |
说白了,如果你有10种功能需要组合,用继承你需要写2^10=1024个类。而用装饰器,你只需要写10个装饰器,然后像搭积木一样组合它们。
我的经验:在实际项目中,我通常把装饰器用在横切关注点上——比如日志、权限、缓存、事务。这些功能往往和业务逻辑无关,但又散落在各个地方。用装饰器把它们集中管理,代码会干净很多。
多个装饰器的执行顺序
当一个函数有多个装饰器时,执行顺序是从下往上的。你想想看:
@decorator_a
@decorator_b
@decorator_c
def my_function():
pass
# 等价于
my_function = decorator_a(decorator_b(decorator_c(my_function)))
执行时,先进入decorator_a,然后decorator_b,最后decorator_c。返回时则相反。这个顺序要记清楚,我曾经因为搞反了顺序,调试了半天才发现是装饰器顺序的问题。
总结
装饰器模式是Python中非常实用的设计模式。它让你能够在不修改原有代码的情况下,灵活地扩展功能。Python的语法糖让装饰器的使用变得异常简洁,但背后的思想——开闭原则,才是真正值得你深入理解的。
记住:好的设计不是一次写出来的,而是通过不断扩展演化出来的。装饰器模式给了你一种优雅的扩展方式。
公众号:蓝海资料掘金营,微信deep3321