Python设计模式
几乎每个 Python 开发者都撞过那堵墙:一次"很简单"的重构,最后膨胀成两周的烂摊子。你改了一个类,十一个调用方跟着报错。问题的解药通常不是更聪明,而是结构——在 Python 的世界里,这种结构就是被语言特性重新裁剪过的设计模式目录。坊间很多教程把《设计模式》(Gang of Four)那本书用 Java 的例子翻来覆去讲,放在 Python 里反而别扭。这篇文章不绕弯子,直接用现代 Python(dataclass、Protocol、FastAPI)讲清楚哪些模式真正值得用,以及"想太多"到底要付出什么代价。
在多范式语言里,模式为什么仍重要
Python 很特别,一个文件里你可以写成面向对象、函数式或过程式,鸭式类型又让你几乎不需要显式接口。这种灵活性恰恰是模式重要的原因。模式不是你必须遵守的规矩,而是被验证过的、能让你看懂大代码库的形状。没有一致的结构,Python 项目很容易漂成一团互相 import 的烂泥,认知负担涨到没人敢碰代码。GoF 书里分了 23 个模式,但生产级 Python 真正重度依赖的其实就那么几个:Django 的通用视图大量用了模板方法,而 Flask、FastAPI 则靠装饰器和依赖注入,一个动作覆盖多个模式。把这些最常用的练好,你会在几乎每个框架里都认得出它们的价值。

创建型模式:用 Python 的方式写工厂与单例
创建型模式处理"创建对象",听起来琐碎,直到你要用十种方式建同一类对象。工厂模式把这段逻辑集中起来,让调用方不关心具体拿到哪个类。在 Python 里,你常常能用简单的函数或字典分发表替代基于类的工厂,更短也更清楚。单例模式(保证一个类只有唯一实例)既出名又常被滥用:在 Python 里,模块级实例早就是单例了,代码还更少。我的建议是写一个普通类、在模块作用域放一个实例就够了,除非你确实需要懒加载或线程安全创建。更大的一课是:Python 里的模式多半靠"约定"而非"机制"——标准库通常已经给你更简单的原语,最强的创建型技能是知道什么时候改用内置工具,而不是新造一个。

结构型模式:适配器与装饰器的日常用法
结构型模式组织对象之间的关系,其中两个几乎出现在每个真实 Python 代码库里。适配器(Adapter)包装一个接口,让两个"语言不通"的系统能协作——比如给 json 模块传一个自定义 default 函数就能处理 datetime 对象,而不用重写你的类。装饰器(Decorator)在不改类的前提下给对象加行为,Python 甚至为此造了 @ 语法,FastAPI 的 @app.get() 和依赖注入装饰器就是例子,日志、缓存、重试逻辑里也到处是它。实践要点是优先组合和包装,而不是继承链——Python 里的深层继承很快变得脆弱,超过两层就该转向适配器或装饰器。想系统补齐 Python 基础,可参考中文站的 Python 入门指南 和 用 AI 学 Python。

行为型模式:观察者、策略与命令
行为型模式管对象之间怎么通信,是现代应用里最值钱的部分。观察者(一个对象通知其他对象状态变化)是事件驱动系统的骨架,在 Python 里用回调列表、信号或 blinker 库就能实现;策略(运行时切换算法)因为函数是一等公民,可以直接传一个可调用对象当策略,而不用搞类层次;命令(把请求封装成对象)很适合撤销栈和任务队列,functools.partial 给你一个轻量方式把参数和可调用对象打包。三者都能让你在不改已有类的前提下持续加行为——这正是开闭原则。多数代码库里,回调、队列、函数参数的开销比正式类层次低得多,后来维护的人会感谢你。

模式对比:经典 GoF 与现代 Python 原生能力
| 需求 | 经典 GoF 模式 | 现代 Python 更省的做法 |
|---|---|---|
| 创建对象 | Abstract Factory 抽象工厂 | 工厂函数 + 字典分发表 |
| 唯一实例 | Singleton 单例 | 模块级单例实例 |
| 加行为不改类 | Decorator 装饰器 | @ 语法、包装函数 |
| 事件通知 | Observer 观察者 | 回调列表 / blinker |
| 指定接口 | Interface 接口 | typing.Protocol 结构子类型 |
| 封装请求 | Command 命令 | functools.partial / 闭包 |
最大变化是语言自己提供了能替代一堆样板代码的原语。Python 3.7 的 dataclass 从几个类型标注就白送 __init__、__repr__ 和相等判断,把老模式常产出的重复代码砍得干净;typing.Protocol 支持结构子类型,你定义一个期望接口而不用强制继承;FastAPI 和 asyncio 把你推向依赖注入和中间件这类"运行时模式"。实用规则是让模式解决真问题,而不是强行给本适合函数式的代码套上 OOP 的壳。能看出"一个带回调的函数"往往就是最好的工厂与最好的策略,说明你已经从"背模式"进阶到"判断模式"。结合我们的 数据分析基础 一起练,能让你的类设计和数据处理都更健壮。

模式被滥用的真实代价
模式失败,通常是加入了没人需要的间接层。经典错误:给本该是普通对象加参数的场景硬上单例;给永远只产一种对象的逻辑包一层抽象工厂。过度设计表现为代码难追踪、难测试、新人难上手,代价可量化:每层间接都增加调试器要跳的函数数、测试要 mock 的对象数。实践上我劝团队先做最朴素的正确版本,等出现第二个真实消费者再上模式。YAGNI(你不会需要它)不是懒惰,而是让生产 Python 可维护的纪律。一个模式真配得上它的位置,是因为两个以上调用方需要同样的抽象,而不是文档说这是最佳实践。想深入打磨可测试性和工程习惯,可延伸阅读中文站的 Python 自动化脚本。
Python 设计模式常用工具表
| 工具 / 框架 | 核心能力 | 定价 |
|---|---|---|
| Django | 全家桶 Web 框架;通用视图用模板方法、模型-视图模式 | 开源免费 |
| FastAPI | 异步 API 框架;装饰器路由与依赖注入 | 开源免费 |
| Blinker | 快速事件分发,实现观察者模式 | 开源免费 |
| PyTest | 基于 fixture 的测试框架,让模式型代码可测 | 开源免费 |
| PyCharm | IDE,带结构搜索和常见模式重构辅助 | 社区版免费;专业版约 ¥600/年 |
| Structurizr / Copier | 项目脚手架与模板,应用一致的架构 | 开源免费 |
能真正练出模式直觉的练习
读模式容易忘,动手实现才记得牢。三个练习给你持久技能:一是拿一个小脚本,把几个相关对象的构造重构成带分发表的工厂函数,量一量代码是否更短更可测;二是把一个有泄漏的类层次用适配器改成组合,把基类整个删掉,让每个组件独立成立;三用回调或 blinker 搭一个观察者小事件系统,让两个不相干的模块对同一事件各自响应。每次做完,问自己:是什么让这个模式赢得了或丢掉了位置?这种自我评估的习惯,比模式本身更值钱,也正是一个人让代码库一个决策接一个决策变得更干净的同一种直觉。如果你想从零把语言基础打牢再谈模式,推荐参考中文站的 Python 入门指南。
常见问题
现代 Python 里 GoF 那 23 个模式还过时吗?
不过时,但被重组了。把 23 个全背上很费时间,真正重要的四五种——工厂、适配器、装饰器、观察者、策略——就覆盖了大多数生产需求。现代 Python 的 dataclass、Protocol 和一等函数,能比原始的基于类示例更简洁地实现许多模式。
Python 里该不该用单例模式?
很少用。模块级实例本身就是单例(模块只被 import 一次),更好读更好测。只有当你需要懒加载、精确的线程安全初始化或子类控制时,才值得上正式单例。真实项目里大多数"单例"更适合写成按需传递的普通对象。
设计模式怎么提升代码可测性?
好模式减少隐藏耦合。接受可调用对象的策略、包装外部 API 的适配器,都让测试里容易注入假对象;依赖注入和观察者回调都给测试代码干净的"接缝"。这正是模式型系统往往比混乱过程式代码更容易验证的原因。
我是先学模式还是先打牢 Python 基础?
先掌握 Python 基本功:函数、类、dataclass、装饰器,以及 collections、itertools 模块。模式建立在这些工具之上,基础不牢时硬学模式只会受挫。语言地基扎实后,模式会像自然延伸而不是玄学。
什么时候该重构引入一个模式?
出现第二个真实消费者,或缺乏结构已经在制造 bug 时。如果你只是"预感"未来需要而眼下没有任何调用方,忍住。模式由反复出现的真实需求来论证,而不是凭猜测;所以只在这段代码证明自己需要一个模式时,才重构进去。