不少开发者在 Python 中实现设计模式时,容易硬套 Java 或 C++ 的经典写法。然而,随着 Python 3.8 ~ 3.12 引入了类型注解(Type Hints)、结构化模式匹配(Pattern Matching)以及增强的元编程与装饰器,传统设计模式的实现方式已经发生了深刻演进。
本文将聚焦单例(Singleton)、工厂(Factory)和观察者(Observer)三大核心模式,探讨它们在现代 Pythonic 风格下的最佳实现方案。
一、 单例模式(Singleton):从 new 重载到模块级单例与装饰器
传统 Java 风格的单例模式通常使用类方法与双重检查锁(DCL)。但在 Python 中,这种写法过于繁琐且不够 Pythonic。
- 传统写法(过度设计)
通过重载 new 方法实现:
class TraditionalSingleton:
_instance = None
def new(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().new(cls)
return cls._instance
- 现代 Python 最佳实践
在现代 Python 中,有两种更简洁、优雅的演进方案:
方案 A:利用 Python 模块天然单例(最推荐)
Python 的模块在第一次导入时会被加载并执行,后续导入均会复用内存中的 sys.modules 对象,模块本身就是天生的线程安全单例。
config_service.py
class _ConfigService:
def init(self):
self.settings = {"theme": "dark", "debug": True}
实例化对象
config_service = _ConfigService()
其他文件直接导入实例使用
from config_service import config_service
方案 B:泛型类装饰器(带类型提示)
如果必须以类的形式存在,使用带有 TypeVar 和 Callable 类型注解的装饰器是更具扩展性的选择:
from typing import Type, TypeVar, Any, Dict
T = TypeVar('T')
def singleton(cls: TypeT) -> Callable..., T:
instances: DictAny, Any = {}
def get_instance(*args: Any, **kwargs: Any) -> T:
if cls not in instances:
instancescls = cls(*args, **kwargs)
return instancescls
return get_instance
@singleton
class DatabaseConnection:
def init(self, db_url: str):
self.db_url = db_url
二、 工厂模式(Factory):利用 Python 3.10+ 模式匹配彻底告别 if-elif
传统的抽象工厂模式往往包含大量的冗余子类定义,而简单工厂模式又容易掉进 if-elif-else 的地狱。
- 现代演进:结构化模式匹配 + 类型别名
Python 3.10 引入的 match-case 结合类型标注,使得工厂函数兼具强类型校验与极致的读写体验。
from dataclasses import dataclass
from typing import Protocol, Literal
使用 Protocol 定义隐式接口(结构化子类型)
class PaymentProcessor(Protocol):
def process(self, amount: float) -> bool:
...
@dataclass
class AlipayProcessor:
merchant_id: str
def process(self, amount: float) -> bool:
print(f"支付宝支付 {amount} 元,商户号:{self.merchant_id}")
return True
@dataclass
class WeChatPayProcessor:
app_id: str
def process(self, amount: float) -> bool:
print(f"微信支付 {amount} 元,AppID:{self.app_id}")
return True
联合类型限定输入参数
PaymentType = Literal"alipay", "wechat"
现代 Python 工厂:结合 match-case 与 类型校验
def create_payment_processor(pay_type: PaymentType, **config) -> PaymentProcessor:
match pay_type:
case "alipay":
return AlipayProcessor(merchant_id=config.get("merchant_id", "default_id"))
case "wechat":
return WeChatPayProcessor(app_id=config.get("app_id", "default_appid"))
case _:
raise ValueError(f"不支持的支付类型: {pay_type}")
使用示例
processor = create_payment_processor("alipay", merchant_id="10086")
processor.process(199.0)
对比优势:
* 放弃了庞大的继承体系,采用 Protocol 契约编程。
* 利用 match-case 实现更高效的分发与可读性。
三、 观察者模式(Observer):从抽象观察者类到 WeakRef 与 Callables
传统观察者模式要求观察者实现统一的 update 接口,这限制了观察者函数的灵活性。同时,由于强引用导致的对象无法释放(悬挂观察者问题)也是常见坑点。
- 现代演进:弱引用(WeakRef)+ 函数式回调
现代 Python 风格的观察者模式不再强求观察者是一个"类",任何可调用对象(函数、方法、Lambda)均可作为回调;同时使用 weakref 防止内存泄漏。
import weakref
from typing import Callable, Any, List
class EventEmitter:
def init(self):
使用 WeakMethod/weakref 存储,避免循环引用导致内存泄漏
self._listeners: Listweakref.ref = \[\]
def subscribe(self, callback: Callable\[Any, None]) -> None:
"""注册回调,自动识别普通函数与绑定的类方法"""
if hasattr(callback, 'self') and callback.self is not None:
针对类绑定方法使用 WeakMethod
ref = weakref.WeakMethod(callback)
else:
普通函数或 lambda
ref = weakref.ref(callback)
self._listeners.append(ref)
def emit(self, event_data: Any) -> None:
"""触发事件,自动清理失效的弱引用"""
alive_listeners = \[\]
for ref in self._listeners:
callback = ref()
if callback is not None:
callback(event_data)
alive_listeners.append(ref)
更新仅保留存活引用的列表
self._listeners = alive_listeners
----------------- 实战应用 -----------------
class OrderService:
def on_order_created(self, order_id: str):
print(f"日志服务 监听到新订单创建: {order_id}")
def send_email_notification(order_id: str):
print(f"邮件服务 发送订单确认邮件: {order_id}")
初始化事件发射器
event_bus = EventEmitter()
实例化并订阅
logger = OrderService()
event_bus.subscribe(logger.on_order_created) # 类方法
event_bus.subscribe(send_email_notification) # 独立函数
触发事件
event_bus.emit("ORD_20260918_001")
对比优势:
* 解耦彻底:观察者无需继承特定的 Observer 抽象基类。
* 内存安全:结合 weakref.ref 与 weakref.WeakMethod,当观察者对象被销毁时,不会因事件总线的强引用而导致垃圾回收(GC)失效。
四、 总结与选型指南
设计模式的本质是解决特定场景下的偶合问题。在现代 Python 开发中:
* 单例模式:优先考虑 Python 模块粒度的自然单例;需要复用时使用类型安全的类装饰器。
* 工厂模式:结合 Protocol (鸭子类型契约) 与 match-case (模式匹配),取代臃肿的继承体系。
* 观察者模式:以 Callable 自由函数/方法 代替特定接口,并引入 weakref 弱引用 解决内存泄漏隐患。
掌握这些现代 Pythonic 的重构思路,能让你的代码在保持优雅与可读的同时,充分发挥现代 Python 强类型与高性能特性的优势。