4.3 SOLID 在循环里的位置


4.3 SOLID 在循环里的位置

本节摘要:SOLID 常被当成静态的背书口诀,本章把它放回红绿重构的动态坐标系:单一职责与开闭原则很大程度是循环白送的,里氏替换靠一套契约测试反复审,接口隔离与依赖倒置是替身功夫的直接产物。读完你应能说出每条原则"由哪个循环动作兑现、由哪种测试症状预警",并停止为原则而原则的空转。

一、单一职责与开闭:循环白送的两条

**单一职责(S)**在循环里的兑现方式朴素得惊人:一条测试只测一个行为,断言写不下去往往是因为一个函数管了两摊事——2.1 的"最薄切片"与 4.1 的"行为可描述",其实都在逼类和函数收敛职责。你不刻意追求 S,红灯也在按需拆分;反过来,当一个类需要十条以上互不相关的测试才能覆盖,它多半已经越权。

**开闭原则(O)**是策略演练(4.2)的直接产物:变化点被红灯点名后,重构把 if 链改成插槽结构,"加规则不改汇总器"就是 O 的字面形态。值得强调的是顺序——O 不是预先设计的 Decoration,是变化真正出现后重构的结晶。为不存在的变化开插槽,违反的是"只写刚好让测试通过的实现"那条更根本的纪律。

这两条的经验配比是:S 靠红灯日常维护,O 靠第二三次变化到来时的重构兑现。第一次变化时忍住不抽象(重复好过错误的抽象),第二次变化时模式信号出现,第三次动手收拢——"事不过三"是循环里最实用的重构触发器。

二、里氏替换:用同一套测试审所有实现

里氏替换原则(L)说子类型必须能安全顶替父类型,抽象的表述不如一套机械动作:所有实现跑同一组契约测试。pytest 的参数化恰好能实现"测试集 × 实现集"的矩阵:

IMPLEMENTATIONS = [InMemoryCouponRepo, SQLiteCouponRepo, FakeClusterRepo] @pytest.mark.parametrize("repo_cls", IMPLEMENTATIONS, ids=lambda c: c.__name__) class TestCouponRepoContract: def test_save_then_load_roundtrip(self, repo_cls): repo = repo_cls() repo.save(coupon(code="SAVE20", percent=Decimal("0.2"))) assert repo.load("SAVE20").percent == Decimal("0.2") def test_load_missing_returns_none(self, repo_cls): assert repo_cls().load("NOPE") is None

内存版、数据库版、假集群版共享同一组契约用例,谁的实现偏离约定,谁的列当场红。这比 code review 里喊"要满足里氏替换"实在得多——替换安全性在这里是跑出来的,不是推出来的。新实现接入时把类名加进列表,契约一次全审,这是 L 在循环里的正确打开方式。

三、接口隔离与依赖倒置:替身逼出来的窄接口

接口隔离(I):客户端不该被迫依赖它用不到的方法。循环里的兑现者是替身测试——当你在测试里为某个协作方写替身时,替身只需要实现被测代码实际用到的方法。替身写得越轻,说明接口越窄;如果替个存储接口要实现二十个方法而测试只用三个,接口该按客户端拆了。4.1 的"第一个使用者"逻辑在这里再次生效:测试这个使用者只会要求它用得到的,天然是隔离原则的执法者。

依赖倒置(D):高层策略不该依赖低层细节,双方依赖抽象。它的循环痕迹满地都是——Pricing 依赖注入的 blacklist 协议、Checkout 构造参数里的 gatewayInventory 里事件订阅的 notifier。每一次"为了能替身而注入",都是 D 在落地上钉。3.3 的边界线在这里回收:替身只能替拥有协议的东西,所以协议必须先被定义出来——D 不是架构装饰,是可测试性的前置条件。

⚠️ 五条原则一齐上是最常见的过度设计姿势。循环给出的顺序是:S 与 I 多为白送,D 在第一次需要替身时落地,O 等变化到来,L 在多实现并存时用契约测试兜底——按需兑现,别提前还贷。

💡 用测试症状反查原则很高效:构造陪葬品多查 D,替身太肥查 I,实现跑偏查 L 的契约矩阵,函数开膛查 S,加功能必改老代码查 O。症状在前,原则是处方编号。

从症状到处方:两个翻译实例

把"症状反查"落成实例。案例一:灯塔小组的报表函数每加一种格式,单元测试就要改五处——症状是"改功能必改老测试",处方编号是 I(接口太胖,报表客户端被迫认识全部格式细节)。重构方向:按客户端拆成内容与排版两个窄协议,替身从二十个方法的怪物瘦到两三个方法,测试改动的爆炸半径随之归零。案例二:契约矩阵里 SQLite 实现总在"load 缺失返回 None"上红——症状是"某实现永远跑偏",这正是 L 级别的警报:它不是合格的可替换品,要么修到契约达标,要么明确移出实现列表,两头都好,装作看不见最坏。

翻译练习做熟后,SOLID 会从"背诵的五条"变成"五张熟悉的处方笺"——你未必记得条目原文,但看到症状就知道往哪个方向动刀,这才是原则在循环里的正确存在形式。往深一层说,SOLID 与其说是"面向对象的设计原则",不如说是"可测试性的一般规律":任何让测试好写的结构决定,拆开看几乎都落在五条中的某几条上。反过来,写不出测试的地方,几乎必然能对号入座到某条被违反的原则。两套语言说的是同一件事,测试语言更具体,原则语言更抽象——手里同时握着这两套,设计讨论就既接地气又上得了台面。

本节要点回顾

  • S 与 I 是循环日常白送:最薄切片与替身的最小要求自然兑现。
  • O 等变化出现再重构兑现,事不过三,别为假想变化开插槽。
  • L 用参数化契约测试矩阵机械审,替换安全性跑出来而不是推出来。
  • D 是可测试性的前置条件:为替身而注入,注入即倒置。
  • 症状反查原则:测试的疼点是处方笺,SOLID 是药名——按方抓药,不按药名进补。

作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U