Python 设计模式全解析
刚从其他静态语言跳Python的朋友,是不是一翻设计模式就觉得:这怎么能薄一半、还能玩出“花活”?
设计模式是针对软件设计中常见问题的通用解决方案。在 Python 中,我们利用其动态特性,可以用比 Java 更简洁的代码实现这些模式。
👉 完整Python设计模式代码集:faif/python-patterns
一、 创建型模式 (Creational Patterns)
核心目标: 把「谁来创建对象」「怎么创建」「用什么代表」三件事,和系统的业务逻辑调用方彻底分开——这样改个配置换个实现,调用方一行都不用碰。
1. 工厂方法 (Factory Method)
把“创建对象”的逻辑单独包装成函数/类,给调用方留个「按需求取货」的入口就行。
Python极简实现
这里连类都省了,直接用函数+闭包/内置类完成:
def get_serializer(fmt: str):
"""根据格式返回对应的序列化工具"""
if fmt == "json":
return lambda data: f"JSON: {str(data).replace(' ', '')}"
if fmt == "xml":
return lambda data: f"<xml>{data}</xml>"
# 默认返回字符串转义版?不,直接原生str更灵活
return str
# 业务调用方的代码
if __name__ == "__main__":
user = {"name": "Alice", "age": 28}
# 只传格式参数,完全不知道内部用了lambda还是str
json_serializer = get_serializer("json")
print(json_serializer(user))
优缺点与适用
✅ 优点: 屏蔽细节,新增格式(比如yaml)只需要在工厂里加个if,调用方零修改;
❌ 缺点: 简单场景(比如永远只返回json)会显得“小题大做”;
🎯 场景: 根据配置/用户输入/环境变量选实例、API响应格式切换。
2. 抽象工厂 (Abstract Factory)
工厂方法的“升级版工厂”——管理一组「互相关联、必须一起用」的对象(比如“暗黑模式”家族的背景色、文字色、按钮样式)。
Python极简实现
利用Python的动态特性,连“抽象基类”都可以简化(当然实际大项目还是建议加abc模块约束):
# 定义两个「产品族」的成员类
class DarkComponent:
get_bg = lambda self: "#000000"
get_text = lambda self: "#FFFFFF"
class LightComponent:
get_bg = lambda self: "#FFFFFF"
get_text = lambda self: "#000000"
# 抽象工厂(统一返回家族成员的入口)
def get_theme_factory(mode: str):
if mode == "dark":
return DarkComponent
if mode == "light":
return LightComponent
raise ValueError(f"不支持的主题模式: {mode}")
# 业务调用方
if __name__ == "__main__":
# 选一个模式,拿到整个家族的工厂
theme_factory = get_theme_factory("dark")
# 直接取家族里的所有关联组件,绝对不会出现「暗黑背景配亮瞎眼的黑字」
print(f"背景色: {theme_factory.get_bg()}, 文字色: {theme_factory.get_text()}")
优缺点与适用
✅ 优点: 强制产品家族一起使用,风格/功能绝对统一;
❌ 缺点: 扩展「单个新产品」还好,扩展「整个新产品族」(比如加个粉紫渐变主题)要改所有工厂代码;
🎯 场景: UI换肤系统、多数据库(MySQL/PostgreSQL/Oracle)驱动全家桶、多语言翻译组件。
3. 惰性初始化 (Lazy Evaluation)
对象/数据不提前准备好,第一次用的时候才加载——特别适合“内存/资源很贵,可能永远用不上”的东西。
Python极简实现
用@property装饰器把加载逻辑藏在“属性访问”的背后:
class LargeFileLoader:
def __init__(self, file_path: str):
self.file_path = file_path
# 提前占位,不真的加载
self._content = None
@property
def content(self):
"""第一次访问content属性时才加载文件"""
if self._content is None:
print(f"⏳ 首次加载大文件: {self.file_path}...")
# 真实场景用open,这里模拟
self._content = ["行1", "行2", "行3", "行..."] * 10000
return self._content
# 业务调用方
if __name__ == "__main__":
loader = LargeFileLoader("超大日志.txt")
# 此时还没加载!!
print("对象初始化完成,文件未加载")
# 第一次print才会触发加载
print(f"文件内容前2行: {loader.content[:2]}")
# 第二次直接用缓存,不会再加载
print(f"文件总行数(复用缓存): {len(loader.content)}")
优缺点与适用
✅ 优点: 减少启动时间、降低初始内存占用;
❌ 缺点: 第一次访问会有明显延迟、线程不安全(多线程下可能同时加载两次);
🎯 场景: 加载超大文件、建立昂贵的数据库/网络连接、解析复杂的配置文件。
4. 单例模式 (Singleton)
确保一个类在整个程序生命周期里只有一个实例——适合需要“全局共享唯一状态”的东西。
Python极简(但安全隐患少)的实现
用元类的方式(比__new__更“高级”但更可控,避免多线程和继承问题的简化版):
class SingletonMeta(type):
"""元类:控制类的创建过程"""
_instances = {}
def __call__(cls, *args, **kwargs):
"""当调用类(比如AppConfig())时触发"""
if cls not in cls._instances:
# 先占个位置?或者直接加锁?这里是简化版单线程
cls._instances[cls] = super().__call__(*args, **kwargs)
return cls._instances[cls]
# 实际的单例类
class AppConfig(metaclass=SingletonMeta):
def __init__(self):
# 这里的初始化只会在第一次调用时执行!!
print("🔧 首次加载配置...")
self.db_host = "localhost"
self.db_port = 3306
# 测试单例
if __name__ == "__main__":
config1 = AppConfig()
config2 = AppConfig()
# 两个变量指向同一个对象
print(f"config1 == config2? {config1 == config2}")
print(f"config1的地址: {id(config1)}, config2的地址: {id(config2)}")
优缺点与适用
✅ 优点: 节约内存、保证全局状态唯一、避免重复初始化;
❌ 缺点: 违背单一职责原则(一个类既要管自己的业务,还要管单例)、测试困难(状态共享容易脏测试)、多线程需加锁;
🎯 场景: 全局配置管理、数据库连接池、日志记录器。
二、 结构型模式 (Structural Patterns)
核心目标: 用类继承/对象组合的方式,把零散的小类/对象“拼”成更大、更灵活的结构——不用改原有代码,就能加功能、换结构。
5. 装饰器模式 (Decorator)
动态地给对象/函数「叠加」额外功能——比“继承子类加功能”灵活100倍,因为可以任意顺序叠加多个装饰器。
Python原生支持!极简实现
Python自带@语法糖,直接用函数/类装饰器就行:
# 定义两个装饰器
def bold(func):
"""给返回值加<b>标签"""
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return f"<b>{result}</b>"
return wrapper
def italic(func):
"""给返回值加<i>标签"""
def wrapper(*args, **kwargs):
result = func(*args, **kwargs)
return f"<i>{result}</i>"
return wrapper
# 业务函数,用@语法糖叠加装饰器(注意顺序:从下往上生效)
@bold
@italic
def say_hello(name: str):
return f"Hello, {name}!"
# 测试
if __name__ == "__main__":
print(say_hello("Bob")) # 输出: <b><i>Hello, Bob!</i></b>
优缺点与适用
✅ 优点: 动态叠加、不修改原有代码、符合“开闭原则”;
❌ 缺点: 多个嵌套装饰器会增加调试难度、可能改变原函数的元信息(比如__name__,可以用functools.wraps解决);
🎯 场景: 日志记录、性能监控、权限校验、缓存预热。
三、 行为型模式 (Behavioral Patterns)
核心目标: 关注「对象之间怎么沟通」「责任怎么分配」——让沟通更清晰、责任更明确、代码更易扩展。
6. 迭代器模式 (Iterator)
访问集合/容器里的元素,但完全不暴露容器的内部结构——Python原生几乎所有容器都支持!
Python原生用法 + 自定义简单迭代器
# 1. 原生用法(Python内置容器全支持,比如list/dict/set/str)
if __name__ == "__main__":
print("--- 原生list迭代器 ---")
my_list = [10, 20, 30]
# iter()拿到迭代器对象
it = iter(my_list)
# next()逐个取元素,取完会抛StopIteration异常
print(next(it)) # 10
print(next(it)) # 20
print(next(it)) # 30
# print(next(it)) # 这里会报错
# 2. 自定义简单迭代器(实现__iter__和__next__)
class EvenNumberIterator:
"""生成0到max_num的所有偶数的迭代器"""
def __init__(self, max_num: int):
self.max_num = max_num
self.current = 0
def __iter__(self):
"""必须返回自己,因为迭代器本身也是可迭代对象"""
return self
def __next__(self):
"""返回下一个偶数,超过max_num就抛异常"""
if self.current > self.max_num:
raise StopIteration
result = self.current
self.current += 2
return result
# 测试自定义迭代器
if __name__ == "__main__":
print("\n--- 自定义偶数迭代器 ---")
even_it = EvenNumberIterator(10)
# 可以直接用for循环(Python的for本质上就是调用iter()和next())
for num in even_it:
print(num)
优缺点与适用
✅ 优点: 统一遍历接口、不暴露内部结构、支持多种遍历方式;
❌ 缺点: 对于简单的list/dict,手动实现迭代器显得多余;
🎯 场景: 遍历数据库大表记录(避免一次性加载到内存)、处理大型流式文件、自定义复杂的数据结构。