在软件开发中,随着系统的复杂性增加,需求的变化往往会导致代码的频繁修改。为了提高代码的灵活性和可维护性,设计模式应运而生。其中,装饰模式(Decorator Pattern)是一种非常实用的结构型设计模式,它允许在不改变对象自身的情况下,动态地给对象添加额外的功能。
1. 什么是装饰模式?
装饰模式是一种设计模式,它通过将对象包装在一个装饰器类中来实现功能的扩展。装饰器类可以在调用原始对象的方法之前或之后添加额外的行为。这种方式使得我们可以在运行时动态地改变对象的行为,而不需要修改对象的代码。
装饰模式的核心思想是“将功能分离”。通过将功能封装在不同的装饰器中,我们可以灵活地组合这些装饰器,从而实现复杂的功能。这种方式不仅提高了代码的复用性,还使得系统的扩展变得更加简单。
1.2 装饰模式的组成部分
1.2.1 组件接口(Component)
- 定义:组件接口是装饰模式的核心,它定义了被装饰对象和装饰器的共同接口。这个接口通常包含一个或多个方法,供具体组件和装饰器实现。
- 作用:通过定义统一的接口,装饰模式可以在不改变具体组件的情况下,动态地添加或修改功能。
class Coffee:
def cost(self):
raise NotImplementedError("You should implement this method!")
1.2.2 具体组件(Concrete Component)
- 定义:具体组件是实现组件接口的类,表示需要被装饰的对象。具体组件包含了基本的功能实现。
- 作用:具体组件提供了装饰模式的基础功能,装饰器将在此基础上添加额外的功能。
class SimpleCoffee(Coffee):
def cost(self):
return 5 # 基础咖啡的价格
1.2.3. 装饰器(Decorator)
- 定义:装饰器也是实现组件接口的类,持有一个组件对象的引用,并在其方法中调用该组件的方法。装饰器可以在调用组件的方法之前或之后添加额外的功能。
- 作用:装饰器提供了一个包装层,使得可以在不修改具体组件的情况下,动态地添加功能。
class CoffeeDecorator(Coffee):
def __init__(self, coffee):
self._coffee = coffee # 持有一个组件对象的引用
def cost(self):
return self._coffee.cost() # 调用被装饰的对象的方法
1.2.4 具体装饰器(Concrete Decorator)
- 定义:具体装饰器继承自装饰器类,添加具体的功能或行为。每个具体装饰器可以实现不同的功能,从而为组件提供多种扩展方式。
- 作用:具体装饰器实现了额外的功能,可以在调用组件的方法时添加新的行为。
class MilkDecorator(CoffeeDecorator):
def cost(self):
return self._coffee.cost() + 1 # 添加牛奶的额外费用
class SugarDecorator(CoffeeDecorator):
def cost(self):
return self._coffee.cost() + 0.5 # 添加糖的额外费用
1.2.5 使用示例
# 创建一个基础咖啡
coffee = SimpleCoffee()
print("Cost of simple coffee:", coffee.cost()) # 输出: Cost of simple coffee: 5
# 添加牛奶装饰
milk_coffee = MilkDecorator(coffee)
print("Cost of coffee with milk:", milk_coffee.cost()) # 输出: Cost of coffee with milk: 6
# 添加糖装饰
sugar_milk_coffee = SugarDecorator(milk_coffee)
print("Cost of coffee with milk and sugar:", sugar_milk_coffee.cost()) # 输出: Cost of coffee with milk and sugar: 6.5
1.3 装饰模式的优点
-
灵活性:装饰模式允许在运行时动态地添加或修改对象的行为,而不需要修改对象的代码。这种灵活性使得系统能够快速适应变化的需求。
-
单一职责原则:通过将功能分散到多个装饰器中,可以使每个装饰器只负责特定的功能,符合单一职责原则。这种设计使得代码更加清晰,易于维护。
-
可扩展性:装饰模式允许通过组合不同的装饰器来创建复杂的功能,而不需要创建大量的子类。这种方式使得系统的扩展变得简单,减少了代码的重复。
2. Python 装饰器
装饰器是 Python 中一种强大的功能,它允许您在不修改原始函数代码的情况下增强或修改函数的行为。装饰器通过将额外的功能封装在一个函数中,使得可以在运行时动态地改变对象的行为。
2.1 什么是装饰器?
在 Python 中,装饰器是一种特殊的函数,用于在运行时动态地修改或增强其他函数或方法的功能。装饰器通常用于以下场景:
- 记录日志
- 访问控制和权限检查
- 缓存或记忆化
- 计时和性能监控
装饰器的基本语法使用 @decorator_name
的形式,放在被装饰函数的定义之前。
2.2 装饰器的基本结构
装饰器通常是一个接受函数作为参数并返回一个新函数的高阶函数。下面是装饰器的一般结构:
def my_decorator(func):
def wrapper(*args, **kwargs):
# 在调用原函数之前执行的代码
result = func(*args, **kwargs) # 调用原函数
# 在调用原函数之后执行的代码
return result
return wrapper
2.3 简单的装饰器
下面是一个简单的装饰器示例,它在函数执行前后打印一些信息。
def my_decorator(func):
def wrapper():
print("Something is happening before the function is called.")
func() # 调用被装饰的函数
print("Something is happening after the function is called.")
return wrapper
@my_decorator
def say_hello():
print("Hello!")
# 调用装饰后的函数
say_hello()
输出:
Something is happening before the function is called.
Hello!
Something is happening after the function is called.
解释:
my_decorator
是一个装饰器,它接受一个函数func
作为参数。wrapper
是一个内部函数,它在调用func
之前和之后执行一些操作。- 使用
@my_decorator
语法将say_hello
函数装饰为wrapper
函数。
2.4 带参数的装饰器
带参数的装饰器允许您在装饰器中使用参数,以便根据这些参数改变装饰的行为。
def repeat(num_times):
"""一个装饰器,用于重复执行函数指定的次数"""
def decorator_repeat(func):
def wrapper(*args, **kwargs):
for _ in range(num_times):
result = func(*args, **kwargs) # 调用被装饰的函数
return result # 返回最后一次调用的结果
return wrapper
return decorator_repeat
@repeat(3) # 指定重复执行3次
def greet(name):
"""打印问候信息"""
print(f"Hello, {
name}!")
# 调用装饰后的函数
greet("Alice")
输出:
Hello, Alice!
Hello, Alice!
Hello, Alice!
解释:
repeat(num_times)
是一个带参数的装饰器,它接受一个参数num_times
,表示函数需要被重复执行的次数。decorator_repeat(func)
是实际的装饰器,它接受被装饰的函数。wrapper
函数在调用原始函数func
时重复执行指定的次数。
2.5 使用 functools.wraps
使用装饰器时,原始函数的元数据(如名称和文档字符串)会被覆盖。为了保留这些信息,可以使用 functools.wraps
装饰器。
import functools
def my_decorator_with_wraps(func):
@functools.wraps(func) # 保留原始函数的元数据
def wrapper(*args, **kwargs):
print("Before calling the function.")
result = func(*args, **kwargs)
print("After calling the function.")
return result
return wrapper
@my_decorator_with_wraps
def say_goodbye():
"""This function says goodbye."""
print("Goodbye!")
# 调用装饰后的函数
say_goodbye()
# 查看函数的元数据
print(say_goodbye.__name__) # 输出: say_goodbye
print(say_goodbye.__doc__) # 输出: This function says goodbye.
输出:
Before calling the function.
Goodbye!
After calling the function.
say_goodbye
This function says goodbye.
解释:
- 在
wrapper
函数上使用@functools.wraps(func)
,这样可以保留原始函数的名称和文档字符串。 - 调用
say_goodbye.__name__
和say_goodbye.__doc__
可以验证元数据是否被保留。
2.6 装饰器链
多个装饰器可以叠加在一个函数上,形成装饰器链。
def decorator_one(func):
def wrapper(*args, **kwargs):
print("Decorator One: Before function call")
result = func(*args, **kwargs)
print("Decorator One: After function call")
return result
return wrapper
def decorator_two(func):
def wrapper(*args, **kwargs):
print("Decorator Two: Before function call")
result = func(*args, **kwargs)
print("Decorator Two: After function call")
return result
return wrapper
@decorator_one
@decorator_two
def say_hello():
print("Hello!")
# 调用装饰后的函数
say_hello()
输出:
Decorator One: Before function call
Decorator Two: Before function call
Hello!
Decorator Two: After function call
Decorator One: After function call
解释:
- 在这个例子中,
say_hello
函数被两个装饰器decorator_one
和decorator_two
装饰。 - 调用
say_hello()
时,装饰器的执行顺序是从内到外(即decorator_two
先执行,然后是decorator_one
)。
3. 例子 1:在线购物系统
创建一个简单的在线购物系统,用户可以添加商品到购物车、结算并查看订单。我们将使用装饰器来实现以下功能:
- 输入验证:确保用户输入的商品数量是有效的。
- 日志记录:记录用户的操作日志。
- 性能监控:监控结算操作的执行时间。
import time
from functools import wraps
# 输入验证
def validate_quantity(func):
@wraps(func)
def wrapper(*args, **kwargs):
quantity = args[3] if len(args) >= 4 else kwargs.get('quantity', 0)
if quantity <= 0:
return "Error: Quantity must be greater than zero."
return func(*args, **kwargs)
return wrapper
# 日志记录
def log_action(func):
@wraps(func)
def wrapper(*args, **kwargs):
user = args[1]
print(f"User {
user} is performing an action.")
result = func(*args, **kwargs)
print(f"User {
user} completed the action.")
return result
return wrapper
# 性能监控
def performance_monitor(func):
@wraps(func)
def wrapper(*args, **kwargs):
start_time = time.time()
result = func(*args, **kwargs)
end_time = time.time()
print(f"Execution time for {
func.__name__}: {
end_time - start_time:.4f} seconds")
return result
return wrapper
# 商品类
class Product:
def __init__(self, name, price):
self.name = name
self.price = price
# 购物车类
class ShoppingCart:
def __init__(self):
self.items = {
}
@log_action # 调整装饰器顺序,先记录日志再验证
@validate_quantity
def add_item(self, user, product, quantity):
if product in self.items:
self.items[product] += quantity
else:
self.items[product] = quantity
return f"Added {
quantity} of {
product.name} to the cart."
@performance_monitor
@log_action
def checkout(self, user):
time.sleep(1)
total_cost = sum(product.price * quantity for product, quantity in self.items.items())
self.items.clear()
return f"Checkout successful! Total cost: ${
total_cost:.2f}"
# 使用示例
if __name__ == "__main__":
product1 = Product("Laptop", 1000)
product2 = Product("Headphones", 100)
cart = ShoppingCart()
user = "Alice"
print(cart.add_item(user, product1, 1)) # 正常添加
print(cart.add_item(user, product2, 2)) # 正常添加
print(cart.add_item(user, product2, -1)) # 无效数量
print(cart.checkout(user)) # 结算
User Alice is performing an action.
User Alice completed the action.
Added 1 of Laptop to the cart.
User Alice is performing an action.
User Alice completed the action.
Added 2 of Headphones to the cart.
User Alice is performing an action.
User Alice completed the action.
Error: Quantity must be greater than zero.
User Alice is performing an action.
User Alice completed the action.
Execution time for checkout: 1.0052 seconds
Checkout successful! Total cost: $1200.00
- 输入验证:
validate_quantity
装饰器确保用户输入的商品数量大于零。如果数量无效,返回错误信息。
- 日志记录:
log_action
装饰器在用户执行操作时记录日志,显示用户的操作开始和结束。
- 性能监控:
performance_monitor
装饰器监控结算操作的执行时间,并在控制台输出执行时长。
优点
- 灵活性:通过装饰器,我们可以在不修改
ShoppingCart
类的情况下,动态地添加输入验证、日志记录和性能监控功能。 - 单一职责原则:每个装饰器只负责特定的功能,确保代码的清晰性和可维护性。
- 可扩展性:可以轻松地添加新的装饰器来扩展功能,例如添加新的日志记录方式或其他验证逻辑,而不需要修改现有的代码结构。
4. 例子 2:复杂函数的装饰器传参
def process_order(user, product_name, quantity, price_per_item, discount=0, tax_rate=0.1):
total_price = quantity * price_per_item
total_price -= total_price * (discount / 100) # 应用折扣
total_price += total_price * tax_rate # 应用税费
return f"{
user}, the total price for {
quantity} {
product_name}(s) is: ${
total_price:.2f}"
def log_order(log_level="INFO"):
def decorator(func):
def wrapper(*args, **kwargs):
user = args[0] # 获取用户
product_name = args[1] # 获取产品名称
quantity = args[2] # 获取数量
price_per_item = args[3] # 获取单价
discount = kwargs.get('discount', 0) # 获取折扣
tax_rate = kwargs.get('tax_rate', 0.1) # 获取税率
if log_level == "DEBUG":
print(f"DEBUG: Processing order for {
user}: {
quantity} {
product_name}(s) at ${
price_per_item} each, "
f"with a discount of {
discount}% and a tax rate of {
tax_rate * 100}%.")
elif log_level == "INFO":
print(f"INFO: Processing order for {
user}.")
result = func(*args, **kwargs) # 调用原始函数
return result
return wrapper
return decorator
@log_order(log_level="DEBUG") # 指定日志级别为 DEBUG
def process_order(user, product_name, quantity, price_per_item, discount=0, tax_rate=0.1):
total_price = quantity * price_per_item
total_price -= total_price * (discount / 100) # 应用折扣
total_price += total_price * tax_rate # 应用税费
return f"{
user}, the total price for {
quantity} {
product_name}(s) is: ${
total_price:.2f}"
if __name__ == "__main__":
user = "Alice"
product_name = "Laptop"
quantity = 2
price_per_item = 1000
discount = 10 # 10% 折扣
tax_rate = 0.1 # 10% 税率
# 调用处理订单的函数
result = process_order(user, product_name, quantity, price_per_item, discount=discount, tax_rate=tax_rate)
print(result)
DEBUG: Processing order for Alice: 2 Laptop(s) at $1000 each, with a discount of 10% and a tax rate of 10.0%.
Alice, the total price for 2 Laptop(s) is: $2180.00
- 复杂函数:我们定义了一个复杂的函数
process_order
,它接受多个参数并计算总价。 - 带参数的装饰器:我们创建了一个装饰器
log_order
,它接受一个参数log_level
,用于控制日志的详细程度。 - 参数传递:在装饰器中,我们使用
*args
和**kwargs
来确保能够接受任意数量的位置参数和关键字参数,并在调用原始函数时正确传递这些参数。
*args
:用于接收可变数量的位置参数。它将所有额外的位置参数收集到一个元组中。**kwargs
:用于接收可变数量的关键字参数。它将所有额外的关键字参数收集到一个字典中。
def my_decorator(func):
def wrapper(*args, **kwargs):
# 在调用原始函数之前,可以处理参数
print("Inside decorator:")
print("Position arguments (args):", args) # 打印所有位置参数
print("Keyword arguments (kwargs):", kwargs) # 打印所有关键字参数
# 调用原始函数,并将参数传递给它
result = func(*args, **kwargs)
# 在调用原始函数之后,可以处理返回值
return result
return wrapper
@my_decorator
def greet(name, greeting="Hello"):
return f"{
greeting}, {
name}!"
# 调用被装饰的函数
result = greet("Alice", greeting="Hi")
print(result)
Inside decorator:
Position arguments (args): ('Alice',)
Keyword arguments (kwargs): {
'greeting': 'Hi'}
Hi, Alice!
-
位置参数:
- 当调用
greet("Alice", greeting="Hi")
时,"Alice"
被收集到args
中,成为一个元组('Alice',)
。 args[0]
访问第一个位置参数,值为'Alice'
。
- 当调用
-
关键字参数:
greeting="Hi"
被收集到kwargs
中,成为一个字典{'greeting': 'Hi'}
。kwargs['greeting']
访问关键字参数greeting
,值为'Hi'
。
-
调用原始函数:
- 在装饰器的
wrapper
函数中,使用func(*args, **kwargs)
调用原始函数greet
,并将所有参数传递给它。
- 在装饰器的
5. 例子 3:类函数装饰器传参
def log_method_call(log_level="INFO"):
def decorator(func):
def wrapper(self, *args, **kwargs):
if log_level == "DEBUG":
print(f"DEBUG: Calling method '{
func.__name__}' with arguments: {
args} and keyword arguments: {
kwargs}")
elif log_level == "INFO":
print(f"INFO: Calling method '{
func.__name__}'")
result = func(self, *args, **kwargs) # 调用原始方法
return result
return wrapper
return decorator
class Calculator:
@log_method_call(log_level="DEBUG") # 使用装饰器并传递参数
def add(self, a, b, *args, **kwargs):
print("Additional positional arguments (args):", args)
print("Additional keyword arguments (kwargs):", kwargs)
return a + b
@log_method_call(log_level="INFO") # 使用装饰器并传递参数
def subtract(self, a, b, *args, **kwargs):
print("Additional positional arguments (args):", args)
print("Additional keyword arguments (kwargs):", kwargs)
return a - b
if __name__ == "__main__":
calc = Calculator()
# 调用加法方法
result_add = calc.add(5, 3, 10, 20, extra="value")
print("Result of addition:", result_add)
# 调用减法方法
result_subtract = calc.subtract(10, 4, 5, 15, another="value")
print("Result of subtraction:", result_subtract)
DEBUG: Calling method 'add' with arguments: (10, 20) and keyword arguments: {
'extra': 'value'}
Additional positional arguments (args): (10, 20)
Additional keyword arguments (kwargs): {
'extra': 'value'}
Result of addition: 8
INFO: Calling method 'subtract'
Additional positional arguments (args): (5, 15)
Additional keyword arguments (kwargs): {
'another': 'value'}
Result of subtraction: 6
-
装饰器的灵活性:通过使用
*args
和**kwargs
,装饰器能够处理任意数量的位置参数和关键字参数。这使得装饰器在处理类方法时非常灵活,可以适应不同的调用场景。 -
参数传递:在
Calculator
类的add
和subtract
方法中,*args
收集了额外的位置参数,而**kwargs
收集了额外的关键字参数。这使得方法能够处理更多的输入,而不需要事先定义所有可能的参数。 -
日志记录:装饰器根据
log_level
参数的不同,提供了不同级别的日志信息。在调试时,可以选择更详细的日志输出,而在生产环境中,可以选择更简洁的日志信息。 -
代码复用:通过使用装饰器,我们可以将日志记录的逻辑与业务逻辑分离,使得代码更加清晰和可维护。装饰器可以轻松地应用于其他方法,而无需重复编写日志记录的代码。