91·模块十七:安全与最佳实践高级

常见设计模式在 Python 中的应用

design-patternssoftware-engineering

第 91 课 - 常见设计模式在 Python 中的应用

学习目标

  1. 理解设计模式的核心价值:为什么要在代码中应用设计模式。
  2. 掌握 Python 中 单例模式工厂方法模式观察者模式 的典型实现与应用场景。
  3. 学会根据代码的具体需求,选择合适的设计模式来提升代码的可维护性、灵活性和可扩展性。

核心概念

设计模式是软件工程中针对常见问题总结出的、可重用的解决方案模板。它们不是可以直接复制的代码,而是指导我们如何组织代码结构的"最佳实践"。在 Python 中,由于其动态特性,一些模式的实现比静态语言(如 Java)更加简洁。

1. 单例模式

  • 通俗解释:确保一个类只有一个实例,并提供一个全局访问点。就像公司的 CEO,无论你问公司里的哪个人,他们指的 CEO 都是同一个人。
  • 使用场景:管理共享资源,如数据库连接池、全局配置管理器、日志记录器等。

2. 工厂方法模式

  • 通俗解释:定义一个创建对象的接口,但让子类决定实例化哪一个类。工厂方法让类的实例化延迟到子类。就像一个汽车工厂,客户只需要告诉工厂需要"轿车"还是"SUV",具体制造哪款车型由工厂内部决定。
  • 使用场景:当你需要创建多种类型的对象,并且希望将对象的创建过程与使用过程解耦时。

3. 观察者模式

  • 通俗解释:定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并被自动更新。就像你订阅了一个新闻网站,每当有新新闻发布,你的邮箱就会收到通知。
  • 使用场景:事件处理系统、用户界面与业务逻辑的分离、消息推送等。

代码示例

# 示例 1:单例模式(使用 __new__ 方法实现)
class DatabaseConnection:
    """数据库连接单例"""
    _instance = None

    def __new__(cls, *args, **kwargs):
        if not cls._instance:
            # 第一次创建时,实例化对象
            cls._instance = super().__new__(cls)
            print("创建新的数据库连接实例")
        else:
            print("使用已有的数据库连接实例")
        return cls._instance

    def __init__(self, host="localhost", port=3306):
        # 注意:init 会在每次获取实例时都被调用
        self.host = host
        self.port = port
        self.connected = False

    def connect(self):
        if not self.connected:
            print(f"连接到数据库 {self.host}:{self.port}...")
            self.connected = True

# 测试单例
db1 = DatabaseConnection("127.0.0.1", 3306)
db2 = DatabaseConnection("192.168.1.1", 5432)
print(f"db1 和 db2 是同一个对象: {db1 is db2}")  # 输出 True
db1.connect()  # 连接
print(f"db2 的连接状态: {db2.connected}")  # 输出 True, 因为 db1 和 db2 是同一个对象
# 示例 2:工厂方法模式
from abc import ABC, abstractmethod

# 产品抽象基类
class PaymentProcessor(ABC):
    @abstractmethod
    def process_payment(self, amount: float) -> str:
        pass

# 具体产品类
class CreditCardProcessor(PaymentProcessor):
    def process_payment(self, amount: float) -> str:
        return f"使用信用卡支付了 {amount} 元。"

class AliPayProcessor(PaymentProcessor):
    def process_payment(self, amount: float) -> str:
        return f"使用支付宝支付了 {amount} 元。"

# 工厂抽象基类
class PaymentProcessorFactory(ABC):
    @abstractmethod
    def create_processor(self) -> PaymentProcessor:
        pass

# 具体工厂类
class CreditCardFactory(PaymentProcessorFactory):
    def create_processor(self) -> PaymentProcessor:
        return CreditCardProcessor()

class AliPayFactory(PaymentProcessorFactory):
    def create_processor(self) -> PaymentProcessor:
        return AliPayProcessor()

# 客户端代码
def process_order(payment_type: str, amount: float):
    """客户端只需要关心'类型',不关心具体实现"""
    if payment_type == "credit_card":
        factory = CreditCardFactory()
    elif payment_type == "alipay":
        factory = AliPayFactory()
    else:
        raise ValueError("不支持的支付方式")

    processor = factory.create_processor()
    return processor.process_payment(amount)

# 测试
print(process_order("credit_card", 100.50))
print(process_order("alipay", 200))
# 示例 3:观察者模式(使用 Python 特性简化实现)
class EventEmitter:
    """事件发射器(被观察者/主题)"""
    def __init__(self):
        self._events = {}

    def on(self, event_name: str, callback):
        """注册事件监听器(订阅)"""
        if event_name not in self._events:
            self._events[event_name] = []
        self._events[event_name].append(callback)

    def emit(self, event_name: str, *args, **kwargs):
        """触发事件(发布)"""
        if event_name in self._events:
            for callback in self._events[event_name]:
                callback(*args, **kwargs)

# 观察者/监听器
def on_data_saved(data):
    print(f"[日志记录器] 数据已保存: {data}")

def on_data_saved_notification(data):
    print(f"[通知服务] 您的数据 {data} 已成功保存。")

# 使用示例
data_bus = EventEmitter()

# 注册观察者
data_bus.on('data_saved', on_data_saved)
data_bus.on('data_saved', on_data_saved_notification)

# 当事件发生时
data_bus.emit('data_saved', {"user": "Alice", "id": 123})
# 输出:
# [日志记录器] 数据已保存: {'user': 'Alice', 'id': 123}
# [通知服务] 您的数据 {'user': 'Alice', 'id': 123} 已成功保存。

实践练习

练习 1:基础实现 请实现一个简单的配置管理器单例。要求:

  1. 类名为 ConfigManager
  2. 它从一个字典中加载配置。
  3. 确保无论在哪里实例化,它都持有相同的配置数据。
# 预期测试代码
config1 = ConfigManager({"db": "localhost", "port": 3306})
config2 = ConfigManager({}) # 不应该覆盖 config1 的配置
print(config1.get("db")) # 应输出 localhost
print(config1 is config2) # 应输出 True

练习 2:模式应用 使用工厂方法模式创建一个简单的形状工厂。

  1. 定义抽象产品类 Shape,它有一个方法 area()
  2. 创建具体产品类 CircleRectangle
  3. 创建 ShapeFactory,根据传入的字符串("circle""rectangle")返回相应的形状对象。
  4. 客户端代码只需告诉工厂要什么形状,无需知道具体类名。

练习 3:综合挑战 基于示例 3 的 EventEmitter,实现一个简单的用户注册系统

  1. 创建一个 UserService 类,它有一个 register(user_info) 方法。
  2. 当用户注册成功(在 register 方法末尾),通过 EventEmitter 触发 "user_registered" 事件。
  3. 创建两个监听器:
    • EmailNotifier: 收到事件后,打印 "发送欢迎邮件给 {email}"。
    • ActivityLogger: 收到事件后,打印 "用户 {username} 已于 [当前时间] 注册"。

常见错误

  1. 滥用设计模式:不是所有问题都需要设计模式。简单的代码用复杂模式实现,只会增加不必要的复杂性。请先思考当前代码是否真的遇到了设计模式所针对的问题。
  2. 单例模式的过度使用:过度使用单例会导致代码耦合度变高,难以测试(因为它可能持有状态)。考虑依赖注入是否是更好的选择。
  3. 忽略 Python 的动态特性:在 Python 中,有时一个简单的函数或一个类装饰器就能达到类似"工厂"或"观察者"的效果,不必严格遵循 GoF(《设计模式》)中的类结构。我们的目标是解决问题,而非套用模式

小结

  • 设计模式是解决常见软件设计问题的、经过验证的最佳实践,能显著提升代码质量。
  • 单例模式 用于控制实例数量,管理共享资源,Python 中常用 __new__ 或元类实现。
  • 工厂方法模式 将对象的创建与使用分离,提供了优秀的扩展性,符合开闭原则。
  • 观察者模式 实现了对象间的松耦合通信,是事件驱动架构的基石。
  • 关键要点:理解模式背后的设计原则(如单一职责、开闭原则)比记住实现代码更重要。在 Python 中,应灵活运用其语言特性,以最 Pythonic 的方式解决问题。

练习编辑器

python
Loading...

继续学习

完成本课后,建议继续学习下一课「项目实战:开发一个命令行工具(CLI)」 以巩固所学知识。