硬编码依赖导致测试和替换困难,因其强制耦合具体实现;应结合ABC(或Protocol)声明抽象接口,并通过构造函数注入依赖,使调用方只依赖“能做什么”,而非“是谁实现”。

为什么硬编码依赖会让测试和替换变困难
硬编码依赖指在类内部直接调用 requests.Session()、sqlite3.connect() 或实例化具体服务类(如 PaymentProcessorAlipay())。这类写法导致:测试时必须连真实网络或数据库;换支付渠道要改多处代码;类型提示无法约束接口行为,IDE 无补全、mypy 难校验。
用 ABC 定义协议,让调用方只认“能做什么”
抽象基类(ABC)不是为了强制继承,而是提供运行时可检查的契约。关键点在于:只声明方法签名,不实现逻辑;子类必须实现所有 @abstractmethod;调用方注解用 ABC 类型,而非具体类名。
- 定义
UserRepositoryABC,含get_by_id(self, user_id: int)和save(self, user: User) - 实现
SqliteUserRepo和PostgresUserRepo,都继承该 ABC - 业务类构造函数接收
repo: UserRepository参数,不 import 具体实现模块 - 运行时传入任一子类实例,类型检查器和 IDE 都能识别合法方法
构造函数注入 + ABC 是最稳妥的组合
仅用 ABC 不够——如果类还在内部自己创建实现类,就又回到硬编码。必须配合依赖注入,把控制权交出去。
让 macOS 自动化像说话一样简单 —— 用代码控制你的 Mac,无需学习 AppleScript 系统要求 macOS 14+ (Sonoma) Python 3.9+ 需要开启:辅助功能 + 屏幕录制权限
- 删掉类内
self.repo = SqliteUserRepo()这类语句 - 把
repo作为__init__的必填参数,类型注解为UserRepository - 启动入口(如
main.py)根据环境决定传哪个实现:UserService(SqliteUserRepo())或UserService(PostgresUserRepo()) - 测试时直接传
Mock(spec=UserRepository),无需 patch 或 reload 模块
容易忽略的细节:ABC 的 __subclasshook__ 和 Protocol 的取舍
Python 3.8+ 推荐优先用 typing.Protocol 替代 ABC,除非你需要运行时 isinstance() 检查。Protocol 更轻量,且支持结构化匹配(鸭子类型),而 ABC 要求显式 class X(ABC) 继承或注册。
- 若只需类型提示和 IDE 支持,用
class UserRepository(Protocol)更干净 - 若业务逻辑里有
if isinstance(repo, UserRepository): ...,才用 ABC - 别在 ABC 里写具体方法实现——那会诱使子类误用
super().xxx(),破坏解耦初衷 -
__subclasshook__很少需要自定义,多数场景默认行为已足够
真正卡住人的不是写不出 ABC,而是忘记把实例创建逻辑从类内部移出来。依赖声明(ABC)和依赖传递(构造函数参数)必须同时落地,缺一不可。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










