01、Python - 设计模式介绍:从烂代码到企业级架构的蜕变之路
本文为「Python设计模式」系列第一篇,带你从零理解设计模式的本质,掌握六大核心原则,并通过企业级实战代码学会常用模式的落地用法。全文无废话,小白也能直接上手。

文章目录
- [01、Python - 设计模式介绍:从烂代码到企业级架构的蜕变之路](#01、Python - 设计模式介绍:从烂代码到企业级架构的蜕变之路)
-
- 一、痛点场景:你的代码是不是也变成了"屎山"?
- 二、是什么:设计模式到底是什么?
-
- [2.1 专业解释](#2.1 专业解释)
- [2.2 大白话](#2.2 大白话)
- [2.3 GOF:设计模式的起源](#2.3 GOF:设计模式的起源)
- 三、为什么用:设计模式能给你带来什么?
- 四、六大原则:设计模式的灵魂
-
- [4.1 开闭原则(Open Close Principle)](#4.1 开闭原则(Open Close Principle))
- [4.2 里氏代换原则(Liskov Substitution Principle)](#4.2 里氏代换原则(Liskov Substitution Principle))
- [4.3 依赖倒转原则(Dependence Inversion Principle)](#4.3 依赖倒转原则(Dependence Inversion Principle))
- [4.4 接口隔离原则(Interface Segregation Principle)](#4.4 接口隔离原则(Interface Segregation Principle))
- [4.5 迪米特法则(Demeter Principle)](#4.5 迪米特法则(Demeter Principle))
- [4.6 合成复用原则(Composite Reuse Principle)](#4.6 合成复用原则(Composite Reuse Principle))
- 五、三大分类:23种设计模式总览
-
- [5.1 创建型模式(5种)](#5.1 创建型模式(5种))
- [5.2 结构型模式(7种)](#5.2 结构型模式(7种))
- [5.3 行为型模式(11种)](#5.3 行为型模式(11种))
- 六、企业项目实战:五大常用模式代码演示
-
- [6.1 单例模式:数据库连接池管理](#6.1 单例模式:数据库连接池管理)
- [6.2 工厂模式:多渠道支付网关](#6.2 工厂模式:多渠道支付网关)
- [6.3 观察者模式:订单事件通知](#6.3 观察者模式:订单事件通知)
- [6.4 策略模式:多种优惠计算](#6.4 策略模式:多种优惠计算)
- [6.5 装饰器模式:接口权限与日志增强](#6.5 装饰器模式:接口权限与日志增强)
- 七、常用场景教学:什么时候该用什么模式?
- 八、面试官高频面试题
- 九、总结
一、痛点场景:你的代码是不是也变成了"屎山"?
先讲一个真实的故事。
我曾经接手过一个电商项目的支付模块,打开代码的那一刻,我整个人都麻了。一个 pay.py 文件里写了三千多行代码,if-elif-else 嵌套了八层,支付宝、微信、银联、花呗、余额、优惠券的逻辑全部揉在一起。新增一个支付渠道?不好意思,你得在八个地方加判断,改完之后测试跑了三天,还是漏了一个边界条件,线上直接炸了。
这不是个例。几乎每个程序员在职业生涯中都会遇到以下场景:
- 场景一 :项目里到处都是
if type == "A" ... elif type == "B" ...,每加一种类型就要改半个项目的代码,改完还不知道哪里会崩。 - 场景二 :数据库连接被创建了十几次,内存占用飙升,排查半天发现每个模块都自己
new了一个连接对象。 - 场景三 :用户下单后要发短信、发邮件、扣库存、加积分,四个逻辑全写在
order.py里,哪天要加个推送通知,又得动核心订单代码。 - 场景四:接手别人的代码,一个类干了十件事,改一个功能牵一发而动全身,测试覆盖率为零,根本不敢动。
这些问题的本质是什么?不是你写代码不够努力,而是你缺少一套被验证过的、可复用的设计思想。 这套思想,就是设计模式。
二、是什么:设计模式到底是什么?
2.1 专业解释
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。它与编程语言无关,是一种通用的解决思路,是先辈们在实践中总结出的精华,综合考虑了封装性、复用性、效率性、可修改性、可一致性等各种因素的高度抽象。
简单来说,设计模式不是框架,不是库,更不是什么高深的算法。它是解决特定场景下软件设计问题的可复用方案。
2.2 大白话
如果把写代码比作盖房子,设计模式就是建筑图纸。你不需要每次盖房子都从零研究"承重墙怎么放""水管怎么走",前人已经把这些最优解总结成了图纸,你照着做就行。
再打个比方,设计模式就像棋谱。下象棋的人都知道"马走日象走田",但高手还知道"当头炮马来跳""屏风马"这些固定套路。这些套路不是规则,而是无数人实战中总结出来的最优应对方式。设计模式就是编程界的棋谱。
2.3 GOF:设计模式的起源
1994年,Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 四人合著出版了一本名为《Design Patterns - Elements of Reusable Object-Oriented Software》(中文译名:《设计模式 - 可复用的面向对象软件元素》)的书,该书首次系统地提出了软件开发中设计模式的概念。
四位作者合称 GOF(Gang of Four,四人帮)。他们提出的设计模式主要基于以下两个面向对象设计原则:
- 对接口编程,而不是对实现编程
- 优先使用对象组合,而不是继承
这两句话是整个设计模式体系的基石,后面所有模式都是围绕这两个原则展开的。记住它们,比记住23种模式的名字更重要。
三、为什么用:设计模式能给你带来什么?
很多初学者会问:"我不用设计模式,代码不也能跑吗?"
能跑,和跑得好、维护得起、扩展得了,是两回事。设计模式给你带来的价值是长期的:
| 维度 | 没有设计模式 | 有设计模式 |
|---|---|---|
| 可读性 | 一个类三千行,看了就头疼 | 职责清晰,看类名就知道干什么的 |
| 可维护性 | 改一个bug引出三个新bug | 改动局部化,影响范围可控 |
| 可扩展性 | 加功能要改核心代码 | 新增类即可,不动旧代码 |
| 团队协作 | 每个人写法不同,互相看不懂 | 统一范式,新人也能快速上手 |
| 面试竞争力 | 只会写业务代码 | 能讲出架构思路,薪资上一个档次 |
一句话总结:设计模式不能让你的代码跑起来,但能让你的代码在三年后还能被人维护。
四、六大原则:设计模式的灵魂

设计模式不是凭空发明的,它们都遵循一套核心设计原则。理解了这六大原则,你甚至能自己推导出设计模式。下面每个原则我都会用"专业解释+大白话+生活案例"的方式讲透。
4.1 开闭原则(Open Close Principle)
专业解释:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,而是通过新增代码来实现扩展,达到热插拔的效果。
大白话:写好的代码就别乱动了,要加新功能?写新的去。老代码是经过测试验证的,你改它就有可能引入新bug。
生活案例:你买了一台电脑,想加个硬盘,不需要把电脑拆开重新焊接主板,直接插一根数据线就行。电脑的接口就是"对扩展开放",主板内部电路就是"对修改关闭"。
企业实战 :支付模块支持支付宝、微信,现在要加银联。正确的做法是新增一个 UnionPayClient 类,而不是在原来的 pay() 方法里加 elif。
4.2 里氏代换原则(Liskov Substitution Principle)
专业解释:任何基类可以出现的地方,子类一定可以出现。只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才算真正被复用。
大白话:儿子要能替老子干活。如果老子能做的事儿子做不了,那这个继承关系就是假的,是在滥用继承。
生活案例:父亲是一名司机,会开卡车。儿子继承了父亲的"司机"身份,但儿子只会开小轿车,不会开卡车。那你就不能说儿子能替换父亲,这个继承关系有问题。正确的做法是把"司机"抽象成接口,卡车司机和小轿车司机分别实现。
企业实战 :如果你有一个 Bird 基类有 fly() 方法,那 Ostrich(鸵鸟)就不能继承 Bird,因为鸵鸟不会飞。强行继承会导致调用 fly() 时出问题。
4.3 依赖倒转原则(Dependence Inversion Principle)
专业解释:针对接口编程,依赖于抽象而不依赖于具体。高层模块不应该依赖低层模块,二者都应该依赖其抽象。
大白话:别直接依赖具体的实现类,要依赖接口。就像你插充电器,依赖的是"插座"这个标准接口,而不是依赖某个特定品牌的插座。
生活案例:你家里的电器插头是国标两孔/三孔,不管你换什么牌子的插线板,只要是国标插座就能用。如果电器直接焊死在某个插线板上,换插线板就得把电器也拆了,这就是依赖具体实现的坏处。
企业实战 :业务层不应该直接依赖 MySQLDatabase 类,而应该依赖 Database 接口。这样哪天从 MySQL 切到 PostgreSQL,业务层代码一行都不用改。
4.4 接口隔离原则(Interface Segregation Principle)
专业解释:使用多个隔离的接口,比使用单个庞大的接口要好。降低类之间的耦合度,一个类不应该依赖它不需要的方法。
大白话:接口要小而专,不要大而全。你不需要的方法,就别强迫你实现。
生活案例:你去餐厅吃饭,菜单上有中餐、西餐、日料、韩餐,你只点中餐。如果餐厅要求你必须把所有菜系的菜都点一遍才能吃饭,你肯定不干。接口隔离就是让你只点你需要的菜。
企业实战 :一个 UserService 接口里同时有 login()、register()、deleteUser()、exportUserData()。前台用户只需要 login() 和 register(),却被迫实现了管理后台才用的 deleteUser()。正确做法是拆成 UserAuthService 和 UserAdminService 两个接口。
4.5 迪米特法则(Demeter Principle)
专业解释:又称最少知道原则,一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。只和你的直接朋友通信,不和陌生人说话。
大白话:各干各的,别瞎操心别人的事。你需要什么,直接找对应的人要,别通过A找B找C绕一大圈。
生活案例:你想买一本书,直接去书店买就行。不需要先问邻居"你知道哪个书店好吗",邻居再问他的朋友,朋友再问批发商报价格。绕了三圈,信息还可能传错。
企业实战 :OrderService 要获取用户地址,不应该写 user.getAddress().getCity().getName() 这种链式调用(俗称"火车失事"),而应该让 User 类提供一个 getCityName() 方法,直接调用。
4.6 合成复用原则(Composite Reuse Principle)
专业解释:尽量使用合成/聚合/组合的方式复用代码,而不是使用继承。继承是增加耦合性、减少代码量的一种方式,不要随意滥用。
大白话:能用"组合"就别用"继承"。继承是白盒复用,父类的内部细节对子类可见,耦合度高;组合是黑盒复用,通过对象之间的协作完成功能,耦合度低。
生活案例:你想要一辆带导航的车。方案一:继承"车",造一个"带导航的车"的新子类。方案二:买一台车,再买一个导航仪装上去。方案二就是组合,导航仪坏了可以换,车还能继续用。方案一的话,导航坏了整车都得回厂。
企业实战 :不要为了复用日志功能就让 OrderService 继承 Logger。正确的做法是 OrderService 持有一个 Logger 对象(组合),需要打日志时调用 logger.info()。
五、三大分类:23种设计模式总览

GOF提出的23种设计模式分为三大类:
5.1 创建型模式(5种)
关注"对象怎么创建",将对象的创建与使用分离。
| 模式 | 一句话说明 | 常用场景 |
|---|---|---|
| 单例模式(Singleton) | 确保一个类只有一个实例 | 数据库连接池、配置管理器、日志器 |
| 工厂方法(Factory Method) | 定义创建对象的接口,子类决定实例化哪个类 | 日志记录器、跨平台组件 |
| 抽象工厂(Abstract Factory) | 创建一组相关对象,无需指定具体类 | 跨平台UI组件、数据库方言切换 |
| 建造者模式(Builder) | 分步构建复杂对象 | SQL查询构建器、配置对象、HTTP请求 |
| 原型模式(Prototype) | 通过克隆创建对象 | 复杂对象的复制、原型注册表 |
5.2 结构型模式(7种)
关注"类和对象怎么组合",让结构更合理。
| 模式 | 一句话说明 | 常用场景 |
|---|---|---|
| 适配器模式(Adapter) | 让不兼容的接口一起工作 | 第三方API对接、旧系统兼容 |
| 桥接模式(Bridge) | 分离抽象与实现,让两者独立变化 | 多维度变化场景(如形状+颜色) |
| 组合模式(Composite) | 树形结构统一处理 | 文件系统、组织架构、菜单 |
| 装饰器模式(Decorator) | 动态给对象添加职责 | 中间件、AOP、权限校验、日志增强 |
| 外观模式(Facade) | 提供统一接口简化子系统调用 | SDK封装、复杂系统的统一入口 |
| 享元模式(Flyweight) | 共享细粒度对象减少内存 | 连接池、线程池、文字编辑器字符 |
| 代理模式(Proxy) | 控制对对象的访问 | 缓存代理、权限代理、远程代理 |
5.3 行为型模式(11种)
关注"对象之间怎么通信和分配职责"。
| 模式 | 一句话说明 | 常用场景 |
|---|---|---|
| 责任链模式(Chain of Responsibility) | 请求沿处理链传递 | 中间件、过滤器、审批流程 |
| 命令模式(Command) | 将请求封装为对象 | 撤销/重做、任务队列、宏命令 |
| 解释器模式(Interpreter) | 定义语言的文法解释器 | 规则引擎、表达式解析、SQL解析 |
| 迭代器模式(Iterator) | 遍历集合不暴露内部结构 | Python的for循环、生成器 |
| 中介者模式(Mediator) | 用中介对象封装交互 | 聊天室、调度中心、MVC的Controller |
| 备忘录模式(Memento) | 保存和恢复对象状态 | 编辑器撤销、游戏存档、快照 |
| 观察者模式(Observer) | 状态变化时通知依赖者 | 事件监听、发布订阅、消息通知 |
| 状态模式(State) | 行为随状态改变 | 订单状态机、TCP连接状态、播放器 |
| 策略模式(Strategy) | 封装可互换的算法族 | 支付方式、排序算法、优惠计算 |
| 模板方法(Template Method) | 定义算法骨架,子类实现步骤 | 框架生命周期、数据处理流水线 |
| 访问者模式(Visitor) | 不改变类的前提下增加操作 | 编译器AST遍历、报表统计 |
六、企业项目实战:五大常用模式代码演示
理论说了这么多,下面直接上代码。我挑选了企业项目中最常用的五种模式,每个都给出可直接运行的Python代码。
6.1 单例模式:数据库连接池管理

痛点场景:项目中多个模块各自创建数据库连接,导致连接数爆炸,数据库服务器连接数被打满。
解决方案:使用单例模式,确保全局只有一个连接池实例,所有模块共享。
python
import threading
from typing import Optional
class DatabaseConnectionPool:
"""数据库连接池 - 线程安全的单例模式"""
_instance: Optional["DatabaseConnectionPool"] = None
_lock = threading.Lock()
def __new__(cls, *args, **kwargs):
if cls._instance is None:
with cls._lock:
# 双重检查锁定,避免多线程下重复创建
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self, max_connections: int = 10):
if self._initialized:
return
self.max_connections = max_connections
self._pool = []
self._initialized = True
print(f"[初始化] 连接池创建成功,最大连接数: {max_connections}")
def get_connection(self):
"""从连接池获取连接"""
if self._pool:
return self._pool.pop()
return f"connection-{id(self)}"
def release_connection(self, conn):
"""归还连接到连接池"""
if len(self._pool) < self.max_connections:
self._pool.append(conn)
# 测试:无论创建多少次,都是同一个实例
if __name__ == "__main__":
pool1 = DatabaseConnectionPool(max_connections=20)
pool2 = DatabaseConnectionPool(max_connections=5)
print(f"pool1 is pool2: {pool1 is pool2}") # True
print(f"pool1.max_connections: {pool1.max_connections}") # 20(第一次初始化的值)
Pythonic补充:在Python中,模块本身就是天然的单例。如果不需要类的形式,直接把连接池写在模块级别即可:
python
# db_pool.py
_max_connections = 10
_pool = []
def get_connection():
global _pool
if _pool:
return _pool.pop()
return "new-connection"
def release_connection(conn):
global _pool
if len(_pool) < _max_connections:
_pool.append(conn)
其他模块 from db_pool import get_connection 即可,因为Python模块只会被导入一次。
6.2 工厂模式:多渠道支付网关

痛点场景 :支付模块写了一堆 if payment_method == "alipay",每加一个支付渠道就要改核心代码,违反开闭原则。
解决方案:使用工厂模式,将支付对象的创建与业务逻辑分离。新增支付渠道只需新增类,不动核心代码。
python
from abc import ABC, abstractmethod
from typing import Dict, Type
# ========== 抽象产品 ==========
class PaymentClient(ABC):
"""支付客户端抽象基类"""
@abstractmethod
def pay(self, order_id: str, amount: float) -> Dict:
"""发起支付"""
pass
@abstractmethod
def refund(self, transaction_id: str, amount: float) -> bool:
"""退款"""
pass
# ========== 具体产品 ==========
class AlipayClient(PaymentClient):
"""支付宝支付客户端"""
def __init__(self, app_id: str, private_key: str):
self.app_id = app_id
self.private_key = private_key
def pay(self, order_id: str, amount: float) -> Dict:
print(f"[支付宝] 订单 {order_id} 支付 ¥{amount}")
return {"status": "success", "transaction_id": f"ALIPAY-{order_id}"}
def refund(self, transaction_id: str, amount: float) -> bool:
print(f"[支付宝] 交易 {transaction_id} 退款 ¥{amount}")
return True
class WeChatPayClient(PaymentClient):
"""微信支付客户端"""
def __init__(self, mch_id: str, api_key: str):
self.mch_id = mch_id
self.api_key = api_key
def pay(self, order_id: str, amount: float) -> Dict:
print(f"[微信支付] 订单 {order_id} 支付 ¥{amount}")
return {"status": "success", "transaction_id": f"WXPAY-{order_id}"}
def refund(self, transaction_id: str, amount: float) -> bool:
print(f"[微信支付] 交易 {transaction_id} 退款 ¥{amount}")
return True
class UnionPayClient(PaymentClient):
"""银联支付客户端"""
def __init__(self, mer_id: str, cert_path: str):
self.mer_id = mer_id
self.cert_path = cert_path
def pay(self, order_id: str, amount: float) -> Dict:
print(f"[银联支付] 订单 {order_id} 支付 ¥{amount}")
return {"status": "success", "transaction_id": f"UNION-{order_id}"}
def refund(self, transaction_id: str, amount: float) -> bool:
print(f"[银联支付] 交易 {transaction_id} 退款 ¥{amount}")
return True
# ========== 工厂类 ==========
class PaymentFactory:
"""支付客户端工厂 - 统一管理所有支付渠道的创建"""
_registry: Dict[str, Type[PaymentClient]] = {}
@classmethod
def register(cls, method: str, client_class: Type[PaymentClient]):
"""注册支付渠道(支持动态扩展)"""
cls._registry[method] = client_class
@classmethod
def create(cls, method: str, config: Dict) -> PaymentClient:
"""根据支付方式创建对应的支付客户端"""
client_class = cls._registry.get(method)
if not client_class:
raise ValueError(f"不支持的支付方式: {method}")
return client_class(**config)
# ========== 注册与使用 ==========
PaymentFactory.register("alipay", AlipayClient)
PaymentFactory.register("wechat", WeChatPayClient)
PaymentFactory.register("unionpay", UnionPayClient)
if __name__ == "__main__":
# 业务代码只依赖工厂和抽象接口
configs = {
"alipay": {"app_id": "app123", "private_key": "key123"},
"wechat": {"mch_id": "mch456", "api_key": "key456"},
"unionpay": {"mer_id": "mer789", "cert_path": "/path/to/cert"},
}
for method, config in configs.items():
client = PaymentFactory.create(method, config)
result = client.pay(f"ORDER-{method}", 99.9)
print(f" 结果: {result}\n")
核心优势 :新增支付渠道时,只需写一个新的 XxxPayClient 类并调用 PaymentFactory.register() 注册,业务代码零改动。这就是开闭原则的完美实践。
6.3 观察者模式:订单事件通知

痛点场景 :用户下单后要发短信、发邮件、扣库存、加积分,所有逻辑全写在 order.py 里,每加一个后续操作就要改订单核心代码。
解决方案:使用观察者模式(发布-订阅),订单服务只负责发布事件,各个观察者自行处理。
python
from abc import ABC, abstractmethod
from typing import List, Dict, Any
# ========== 观察者接口 ==========
class EventObserver(ABC):
"""事件观察者抽象接口"""
@abstractmethod
def update(self, event: str, data: Dict[str, Any]):
"""事件回调"""
pass
# ========== 被观察者(事件发布者) ==========
class EventPublisher:
"""事件发布者 - 维护观察者列表并通知"""
def __init__(self):
self._observers: List[EventObserver] = []
def attach(self, observer: EventObserver):
"""订阅事件"""
self._observers.append(observer)
def detach(self, observer: EventObserver):
"""取消订阅"""
self._observers.remove(observer)
def notify(self, event: str, data: Dict[str, Any]):
"""通知所有观察者"""
for observer in self._observers:
observer.update(event, data)
# ========== 具体观察者 ==========
class SmsNotifier(EventObserver):
"""短信通知观察者"""
def update(self, event: str, data: Dict[str, Any]):
if event == "order_created":
print(f"[短信] 发送下单成功短信给用户 {data['user_id']}")
class EmailNotifier(EventObserver):
"""邮件通知观察者"""
def update(self, event: str, data: Dict[str, Any]):
if event == "order_created":
print(f"[邮件] 发送订单确认邮件到 {data['email']}")
class InventoryDeductor(EventObserver):
"""库存扣减观察者"""
def update(self, event: str, data: Dict[str, Any]):
if event == "order_created":
print(f"[库存] 扣减商品 {data['product_id']} 库存 {data['quantity']} 件")
class PointsAdder(EventObserver):
"""积分增加观察者"""
def update(self, event: str, data: Dict[str, Any]):
if event == "order_created":
points = int(data["amount"])
print(f"[积分] 给用户 {data['user_id']} 增加 {points} 积分")
# ========== 订单服务 ==========
class OrderService(EventPublisher):
"""订单服务 - 只负责创建订单并发布事件"""
def create_order(self, user_id: int, product_id: int, quantity: int, amount: float, email: str):
# 1. 创建订单(核心逻辑)
order_id = f"ORD-{user_id}-{product_id}"
print(f"[订单] 创建订单 {order_id},金额 ¥{amount}")
# 2. 发布事件,观察者自行处理
self.notify("order_created", {
"order_id": order_id,
"user_id": user_id,
"product_id": product_id,
"quantity": quantity,
"amount": amount,
"email": email,
})
return order_id
if __name__ == "__main__":
order_service = OrderService()
# 注册观察者(按需添加,互不影响)
order_service.attach(SmsNotifier())
order_service.attach(EmailNotifier())
order_service.attach(InventoryDeductor())
order_service.attach(PointsAdder())
# 创建订单
order_service.create_order(
user_id=1001,
product_id=2001,
quantity=2,
amount=199.8,
email="user@example.com"
)
核心优势 :订单服务完全不需要知道有哪些后续操作。要加一个"推送通知"?写一个 PushNotifier 类,attach 上去就行,订单代码一行都不用改。
6.4 策略模式:多种优惠计算

痛点场景 :促销活动有满减、折扣、优惠券、新人价等多种优惠方式,代码里全是 if promo_type == "discount",活动一多就乱成一锅粥。
解决方案:使用策略模式,将每种优惠算法封装为独立的策略类,运行时动态切换。
python
from abc import ABC, abstractmethod
from dataclasses import dataclass
from typing import Dict, Type
# ========== 策略接口 ==========
class DiscountStrategy(ABC):
"""优惠策略抽象接口"""
@abstractmethod
def calculate(self, original_price: float, context: Dict) -> float:
"""计算优惠后的价格"""
pass
# ========== 具体策略 ==========
class FixedDiscountStrategy(DiscountStrategy):
"""固定金额优惠(如满100减20)"""
def calculate(self, original_price: float, context: Dict) -> float:
threshold = context.get("threshold", 0)
discount = context.get("discount", 0)
if original_price >= threshold:
return max(0, original_price - discount)
return original_price
class PercentageDiscountStrategy(DiscountStrategy):
"""百分比折扣(如打8折)"""
def calculate(self, original_price: float, context: Dict) -> float:
percentage = context.get("percentage", 1.0)
return round(original_price * percentage, 2)
class CouponStrategy(DiscountStrategy):
"""优惠券(固定面额)"""
def calculate(self, original_price: float, context: Dict) -> float:
coupon_amount = context.get("coupon_amount", 0)
return max(0, original_price - coupon_amount)
class NewUserStrategy(DiscountStrategy):
"""新人专享价"""
def calculate(self, original_price: float, context: Dict) -> float:
new_user_price = context.get("new_user_price", original_price)
is_new_user = context.get("is_new_user", False)
return new_user_price if is_new_user else original_price
# ========== 策略上下文 ==========
@dataclass
class DiscountContext:
"""优惠计算上下文 - 持有具体策略并委托计算"""
strategy: DiscountStrategy
def set_strategy(self, strategy: DiscountStrategy):
"""运行时切换策略"""
self.strategy = strategy
def apply_discount(self, original_price: float, params: Dict) -> float:
"""应用优惠"""
return self.strategy.calculate(original_price, params)
# ========== 策略工厂(可选,方便管理) ==========
class DiscountStrategyFactory:
"""优惠策略工厂"""
_strategies: Dict[str, Type[DiscountStrategy]] = {
"fixed": FixedDiscountStrategy,
"percentage": PercentageDiscountStrategy,
"coupon": CouponStrategy,
"new_user": NewUserStrategy,
}
@classmethod
def get_strategy(cls, strategy_type: str) -> DiscountStrategy:
strategy_class = cls._strategies.get(strategy_type)
if not strategy_class:
raise ValueError(f"不支持的优惠类型: {strategy_type}")
return strategy_class()
if __name__ == "__main__":
original_price = 299.0
# 各种优惠场景
scenarios = [
("满减", "fixed", {"threshold": 200, "discount": 50}),
("打折", "percentage", {"percentage": 0.8}),
("优惠券", "coupon", {"coupon_amount": 30}),
("新人价", "new_user", {"new_user_price": 99, "is_new_user": True}),
]
for name, strategy_type, params in scenarios:
strategy = DiscountStrategyFactory.get_strategy(strategy_type)
context = DiscountContext(strategy)
final_price = context.apply_discount(original_price, params)
saved = original_price - final_price
print(f"[{name}] 原价 ¥{original_price} -> 实付 ¥{final_price}(省 ¥{saved:.2f})")
核心优势 :新增一种优惠方式(比如"拼团价"),只需新增一个 GroupBuyStrategy 类并注册到工厂,原有代码不受影响。每种策略独立测试,互不干扰。
6.5 装饰器模式:接口权限与日志增强

痛点场景:每个接口都要写权限校验、日志记录、异常处理的重复代码,一个接口写几十行样板代码,真正的业务逻辑只有几行。
解决方案:使用装饰器模式,将横切关注点(权限、日志、缓存)封装为装饰器,动态叠加到业务函数上。Python的装饰器语法本身就是装饰器模式的天然实现。
python
import time
import functools
from typing import Callable, Set
# ========== 装饰器:权限校验 ==========
def require_permission(permission: str):
"""权限校验装饰器 - 检查用户是否拥有指定权限"""
def decorator(func: Callable) -> Callable:
@functools.wraps(func)
def wrapper(user: dict, *args, **kwargs):
user_permissions: Set[str] = user.get("permissions", set())
if permission not in user_permissions:
raise PermissionError(f"用户缺少权限: {permission}")
return func(user, *args, **kwargs)
return wrapper
return decorator
# ========== 装饰器:日志记录 ==========
def log_execution(func: Callable) -> Callable:
"""执行日志装饰器 - 记录函数名、参数、执行时间"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
start = time.time()
print(f"[日志] 调用 {func.__name__}, 参数: {args}, {kwargs}")
try:
result = func(*args, **kwargs)
elapsed = (time.time() - start) * 1000
print(f"[日志] {func.__name__} 执行成功,耗时 {elapsed:.2f}ms")
return result
except Exception as e:
elapsed = (time.time() - start) * 1000
print(f"[日志] {func.__name__} 执行失败,耗时 {elapsed:.2f}ms,错误: {e}")
raise
return wrapper
# ========== 装饰器:结果缓存 ==========
def cache_result(func: Callable) -> Callable:
"""结果缓存装饰器 - 简单的内存缓存"""
cache = {}
@functools.wraps(func)
def wrapper(*args):
if args in cache:
print(f"[缓存] 命中缓存,直接返回")
return cache[args]
result = func(*args)
cache[args] = result
return result
return wrapper
# ========== 业务函数:叠加多个装饰器 ==========
@log_execution
@require_permission("order:delete")
def delete_order(user: dict, order_id: str):
"""删除订单 - 业务逻辑只有一行"""
print(f"[业务] 删除订单 {order_id}")
return {"status": "deleted", "order_id": order_id}
@log_execution
@cache_result
def get_product_detail(product_id: int):
"""获取商品详情 - 模拟耗时查询"""
print(f"[业务] 从数据库查询商品 {product_id}")
time.sleep(0.5) # 模拟数据库查询耗时
return {"id": product_id, "name": f"商品-{product_id}", "price": 99.9}
if __name__ == "__main__":
# 场景一:有权限的用户删除订单
admin_user = {"id": 1, "name": "admin", "permissions": {"order:delete", "order:view"}}
result = delete_order(admin_user, "ORD-001")
print(f" 结果: {result}\n")
# 场景二:无权限的用户删除订单(会抛异常)
normal_user = {"id": 2, "name": "guest", "permissions": {"order:view"}}
try:
delete_order(normal_user, "ORD-002")
except PermissionError as e:
print(f" 拦截: {e}\n")
# 场景三:缓存效果演示
print("第一次查询:")
get_product_detail(1001)
print("\n第二次查询(应命中缓存):")
get_product_detail(1001)
核心优势 :业务函数只关注业务逻辑,权限、日志、缓存全部通过装饰器叠加。需要加新功能?写个新装饰器,@ 一下就行,业务代码零侵入。
七、常用场景教学:什么时候该用什么模式?
很多人学完23种模式还是不会用,因为不知道"什么时候用"。下面给你一张速查表,按实际开发场景对应到具体模式:
| 业务场景 | 推荐模式 | 为什么 |
|---|---|---|
| 数据库连接池、全局配置 | 单例模式 | 全局唯一,避免资源浪费 |
| 多支付渠道、多消息推送 | 工厂模式 | 动态创建对象,开闭原则 |
| 跨平台UI组件、多数据库方言 | 抽象工厂 | 创建一组相关对象 |
| 复杂对象构建(SQL、HTTP请求) | 建造者模式 | 分步构建,链式调用 |
| 第三方API对接、旧系统兼容 | 适配器模式 | 接口转换,不改原有代码 |
| 中间件、AOP、权限日志 | 装饰器模式 | 动态增强,零侵入 |
| 复杂系统统一入口、SDK封装 | 外观模式 | 简化调用,隐藏子系统复杂度 |
| 事件通知、发布订阅 | 观察者模式 | 解耦发布者和订阅者 |
| 多种算法/优惠/排序 | 策略模式 | 算法可互换,运行时切换 |
| 订单状态、TCP连接状态 | 状态模式 | 消除庞大的if-else状态判断 |
| 审批流程、过滤器链 | 责任链模式 | 请求沿链传递,处理者可动态组合 |
| 撤销/重做、任务队列 | 命令模式 | 请求封装为对象,支持排队和回滚 |
| 框架生命周期、数据流水线 | 模板方法 | 固定骨架,自定义步骤 |
重要提醒:设计模式不是银弹,不要为了用模式而用模式。如果一个场景用简单的函数就能解决,就不要硬套设计模式。过度设计比没有设计更可怕。
八、面试官高频面试题
Q1:简单工厂、工厂方法、抽象工厂有什么区别?
参考答案:
- 简单工厂:一个工厂类根据参数创建不同产品,产品新增时需要修改工厂类,违反开闭原则。适合产品种类固定的场景。
- 工厂方法:定义创建对象的接口,让子类决定实例化哪个类。每个产品对应一个工厂子类,新增产品只需新增工厂类,符合开闭原则。
- 抽象工厂:提供一个接口创建一组相关或相互依赖的对象(产品族),而不需要指定具体类。适合跨平台、多系列产品的场景。
一句话区分:简单工厂造一个产品,工厂方法造一种产品,抽象工厂造一族产品。
Q2:单例模式有哪些实现方式?线程安全怎么保证?
参考答案:
Python中常见的单例实现方式有四种:
- 模块级单例:Python模块天然只导入一次,直接用模块变量即可,最简单最Pythonic。
__new__控制 :重写__new__方法,判断实例是否已存在。- 元类(Metaclass):通过自定义元类控制类的实例化过程。
- 装饰器:用装饰器包装类,维护实例字典。
线程安全保证:使用双重检查锁定(Double-Checked Locking),在 __new__ 中先判断实例是否存在,不存在时加锁,加锁后再次判断。Python中还可以使用 threading.Lock 保证原子性。
Q3:观察者模式和发布订阅模式有什么区别?
参考答案:
- 观察者模式:被观察者(Subject)直接持有观察者列表,状态变化时直接调用观察者的方法。两者是松耦合但直接关联的。
- 发布订阅模式:发布者和订阅者之间通过一个事件通道(Event Bus / Broker)解耦,发布者不知道订阅者的存在,订阅者也不知道发布者。两者完全解耦。
简单说:观察者模式是"你直接通知我",发布订阅是"你发到中间件,我自己去订阅"。
Q4:策略模式和工厂模式有什么区别?
参考答案:
- 工厂模式是创建型模式,关注"对象怎么创建",解决的是对象创建的耦合问题。
- 策略模式是行为型模式,关注"行为怎么选择",解决的是算法/行为的切换问题。
两者经常配合使用:工厂负责创建策略对象,策略负责执行具体算法。比如优惠计算中,工厂根据类型创建策略实例,策略实例执行计算。
Q5:什么是开闭原则?请举例说明。
参考答案:
开闭原则(Open Close Principle):对扩展开放,对修改关闭。即新增功能时应该通过新增代码来实现,而不是修改已有代码。
举例:支付模块原来支持支付宝和微信,现在要加银联。不符合开闭原则的做法是在 pay() 方法里加 elif method == "unionpay"。符合开闭原则的做法是新增 UnionPayClient 类,通过工厂注册,原有代码不动。
Q6:装饰器模式和代理模式有什么区别?
参考答案:
- 装饰器模式:动态地给对象添加额外的职责,关注"增强功能",装饰器和被装饰者实现相同接口,客户端可以透明地使用。
- 代理模式:为其他对象提供一个代理以控制对这个对象的访问,关注"控制访问",代理对象和真实对象实现相同接口,但代理可以决定是否调用真实对象。
两者结构相似,但意图不同:装饰器是"增强",代理是"控制"。Python中的 @decorator 语法是装饰器模式的实现,而 __getattr__ 延迟加载、RPC远程调用是代理模式的典型应用。
九、总结
设计模式不是什么高深莫测的东西,它就是前人踩过无数坑之后总结出来的"最佳实践"。本文从痛点出发,讲清了设计模式的本质、六大核心原则、23种模式分类,并通过五个企业级实战代码演示了最常用的单例、工厂、观察者、策略、装饰器模式。
记住三个关键点:
- 先理解原则,再记忆模式。六大原则是根,23种模式是枝叶。理解了原则,你能自己推导出模式。
- 模式是工具,不是教条。该用的时候用,不该用的时候别硬套。简单问题简单解,复杂问题才需要模式。
- 在实战中学习 。看完本文,去你的项目里找一找:哪里有大段的
if-elif-else?哪里有重复的样板代码?哪里改一个功能牵一发而动全身?找到这些痛点,用对应的模式去重构,你才能真正掌握设计模式。
下一篇我们将深入讲解「普通工厂模式」,从最简单的工厂开始,一步步拆解对象创建的艺术。
转载声明:本文为原创文章,如需转载,请联系作者获得授权,并注明出处。