⌨️ 第五章 从设计到测试
📄 本篇摘要

两个治【面向对象顽疾】的偏方:AOP(面向切面)把日志、事务这类【到处都要、和业务无关】的横切逻辑从业务代码里抽走;DI(依赖注入)不让自己 new 依赖,而是从外面递进来——解耦测试友好。Python 的装饰器就是 AOP 的日常形态。

📝 关系

上一节:命名空间、模块与包 | 下一节:DDD 与值对象

一、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

这是控制反转(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,反转为外部提供。

相关笔记