直接套用solid易写烂代码,因其本质是应对变化与责任的动态判断,而非静态检查清单;硬拆职责、强加无意义抽象、滥用抽象基类、破坏方法契约、忽视构造与返回值一致性,均违背其初衷。

为什么直接套用SOLID术语容易写出更烂的代码
因为SOLID不是检查清单,而是对“变化”和“责任”的持续判断。硬套SingleResponsibilityPrinciple却把process_order()拆成5个类,反而让调用链变长、调试变难;强调LiskovSubstitutionPrinciple却强迫所有子类实现无意义的get_battery_life()(比如DesktopComputer),只是制造虚假抽象。
真正有效的做法是:先写一个能跑通的类,再观察哪里改一次要动三处、哪里加个新类型就得改if-else、哪里测试总得mock一堆不相关的依赖——这些才是SOLID该出手的地方。
用__init__和Protocol落地接口隔离与依赖倒置
很多人以为依赖倒置就是“用抽象代替具体”,结果写出一堆空泛的AbstractPaymentProcessor,最后发现所有子类都只重写一个charge()方法,其余全是NotImplementedError。这不如直接用typing.Protocol定义最小契约。
- 把支付逻辑需要的输入/输出行为抽成
class PaymentHandler(Protocol),只包含process(self, amount: float) -> bool - 订单类
Order的__init__接收handler: PaymentHandler,而非具体类名或字符串 - 测试时传入
Mock()或简易实现,不用动任何继承结构 - 避免用
abc.ABC强制继承——除非你真需要运行时类型检查或想锁死类层级
这样既满足DependencyInversionPrinciple,又不增加抽象噪音;Protocol比抽象基类更轻量,Python 3.8+原生支持,IDE也能跳转到实现。
开闭原则的关键不在“不改代码”,而在“不改已有调用方”
常见错误是给ReportGenerator加export_to_pdf()时,修改了原有generate()方法签名,导致所有调用report.generate()的地方都要加参数。这不是违反开闭,是破坏契约。
- 新增功能应通过新方法、新类或配置驱动(如
format: Literal["csv", "pdf"])暴露 - 若必须扩展行为,优先用组合:
PDFExporter包装ReportData,而不是让ReportGenerator继承PDFMixin - 避免在已有方法里加
if format == "pdf"分支——这会随格式增多而指数级恶化 - 用
functools.singledispatchmethod处理多态分发,比一堆isinstance更易维护
开闭的边界是“调用方是否需要重编译或重写调用逻辑”。只要report.export("pdf")能无缝替代report.generate(),就算成功。
里氏替换失效往往藏在构造函数和返回值里
子类重写save()没问题,但如果FileSaver.save()返回Path,而CloudSaver.save()返回str(比如URL),上层代码用.exists()就会崩——这不是方法行为不一致,是契约没对齐。
- 所有子类
__init__参数必须兼容父类(可用**kwargs兜底,但别删掉必需参数) - 返回类型必须严格一致:用
-> Path | str不如统一返回Uri封装类,哪怕初期只存字符串 - 抛出异常类型也要收敛:
FileSaver抛PermissionError,CloudSaver就不能抛requests.HTTPError,应统一为SaveFailedError - 用
mypy检查协变/逆变,尤其涉及list[Base]和list[Derived]赋值时
最常被忽略的是初始化阶段——子类悄悄多读了一个配置项、少校验一个字段,调用方根本意识不到,直到上线后某天环境变量缺失才暴露。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











