Python设计模式:装饰模式

在软件开发中,随着系统的复杂性增加,需求的变化往往会导致代码的频繁修改。为了提高代码的灵活性和可维护性,设计模式应运而生。其中,装饰模式(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_onedecorator_two 装饰。
  • 调用 say_hello() 时,装饰器的执行顺序是从内到外(即 decorator_two 先执行,然后是 decorator_one)。

3. 例子 1:在线购物系统

创建一个简单的在线购物系统,用户可以添加商品到购物车、结算并查看订单。我们将使用装饰器来实现以下功能:

  1. 输入验证:确保用户输入的商品数量是有效的。
  2. 日志记录:记录用户的操作日志。
  3. 性能监控:监控结算操作的执行时间。
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
  1. 输入验证
    • validate_quantity 装饰器确保用户输入的商品数量大于零。如果数量无效,返回错误信息。
  2. 日志记录
    • log_action 装饰器在用户执行操作时记录日志,显示用户的操作开始和结束。
  3. 性能监控
    • 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
  1. 复杂函数:我们定义了一个复杂的函数 process_order,它接受多个参数并计算总价。
  2. 带参数的装饰器:我们创建了一个装饰器 log_order,它接受一个参数 log_level,用于控制日志的详细程度。
  3. 参数传递:在装饰器中,我们使用 *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 类的 addsubtract 方法中,*args 收集了额外的位置参数,而 **kwargs 收集了额外的关键字参数。这使得方法能够处理更多的输入,而不需要事先定义所有可能的参数。

  • 日志记录:装饰器根据 log_level 参数的不同,提供了不同级别的日志信息。在调试时,可以选择更详细的日志输出,而在生产环境中,可以选择更简洁的日志信息。

  • 代码复用:通过使用装饰器,我们可以将日志记录的逻辑与业务逻辑分离,使得代码更加清晰和可维护。装饰器可以轻松地应用于其他方法,而无需重复编写日志记录的代码。