两个治【面向对象顽疾】的偏方:AOP(面向切面)把日志、事务这类【到处都要、和业务无关】的横切逻辑从业务代码里抽走;DI(依赖注入)不让自己 new 依赖,而是从外面递进来——解耦测试友好。Python 的装饰器就是 AOP 的日常形态。
一、AOP:横切关注点的手术
问题现场:每个业务函数都要打日志、记耗时、开事务——同样的胶水代码复制二十遍(5-11 的重复坏味道,但靠提取函数治不了:它黏在每个函数的出入口)。这类【无处不在但与业务无关】的逻辑叫横切关注点(cross-cutting concern)。
AOP(Aspect-Oriented Programming)的解法:把横切逻辑抽成【切面】,声明式地【织入】目标方法——业务代码一行不写日志,日志自动发生在每个方法前后。Java 的 Spring AOP 是工业级代表。
Python 的天然答案:装饰器——它就是在函数外包一层逻辑:
def log(func): # 切面:日志
def wrapper(*args, **kwargs):
print(f"[进入] {func.__name__}")
result = func(*args, **kwargs)
print(f"[退出] {func.__name__}")
return result
return wrapper
@log # 织入:一行声明
def pay(order_id): # 业务代码干净如初
return f"订单 {order_id} 支付成功"
二、DI:依赖从外面递进来
问题现场:OrderService.__init__ 里 self.db = MySQLDatabase()——类自己焊死了依赖。后果:想换 PostgreSQL 要改源码;想单测却必须连真数据库(5-3 的【可重复】原则当场破产)。
DI(Dependency Injection,依赖注入)的解法:依赖不自己造,从构造参数递进来:
class OrderService:
def __init__(self, db): # 依赖注入:要什么由外面给
self.db = db
- 生产环境:
OrderService(MySQLDatabase()); - 测试环境:
OrderService(FakeDB())——不连库也能测业务逻辑。
这是控制反转(IoC)的一种:对象的依赖控制权从【自己 new】反转到【外部提供】。Java 的 Spring 容器自动化了这一步;Python 简单场景直接传参即可,无需框架。
三、动手 Demo:DI 前后的测试对比
# —— 焊死的版本(反面教材)——
class MySQLDB:
def save(self, data):
return "已写入真库"
class ServiceBad:
def __init__(self):
self.db = MySQLDB() # 焊死:测试必须连真库
def register(self, name):
return self.db.save({"name": name})
# —— 注入的版本 ——
class FakeDB: # 测试替身:行为可控
def __init__(self):
self.saved = []
def save(self, data):
self.saved.append(data)
return f"假库收到 {data['name']}"
class ServiceGood:
def __init__(self, db):
self.db = db
def register(self, name):
return self.db.save({"name": name})
print(ServiceBad().register("波仔")) # 生产形态
svc = ServiceGood(FakeDB()) # 测试形态:换假库
print(svc.register("波仔"))
print(svc.register("小红"))
print("假库记录数:", len(svc.db.saved)) # 断言随手可做
沙箱实跑输出:
已写入真库
假库收到 波仔
假库收到 小红
假库记录数: 2
看到什么算成功:注入版连假库跑通业务流程,还能顺手断言记录数。
读结果:ServiceGood 的代码一个字没改就切换了数据源——这就是【依赖接口不依赖实现】。单测从此不再连真库:DI 不是为了优雅,是为了可测试。
四、30 秒实验
给 FakeDB 加一条规则:名字为【测试】时抛异常——再 register【测试】并 try/except。测试替身可以模拟任何故障场景(真库可没这么听话)。
五、常见误区
| 误区 | 真相 |
|---|---|
| AOP 是新范式取代 OOP | 不是取代是补充:业务仍用对象建模,横切逻辑走切面 |
| DI 必须上框架 | Python 直接构造参数传;框架(如 Spring)只是自动化了装配 |
| 装饰器只是语法糖 | 它是 AOP 思想的轻量落地:织入时机、重复利用全都有 |
自测三问(点击展开)
1. **横切关注点指什么?** 答:日志、事务、鉴权这类到处要用但与业务无关的逻辑——复制粘贴治不了,AOP 抽切面。 2. **DI 解决哪两个具体问题?** 答:换实现不改源码;单测可用测试替身(不连真库)。 3. **控制反转(IoC)反的是什么?** 答:依赖的创建控制权——从类内部自己 new,反转为外部提供。- ☐ 5-23 学习完成
相关笔记
- 单元测试与集成测试 —— 测试替身让【可重复】成立
- 多态与接口 —— DI 注入的依赖本质是接口
- ⌨️ 编程原理 · 索引 —— 全课路线图