用途
arch-solid 技能用于在讨论面向对象设计原则或组件级结构时提供参考,适用于出现 “SOLID”“Single Responsibility”“Open-Closed”“Liskov” 等表述的场景。它总结的是塑造健康面向对象与组件设计的五项原则,并强调应结合判断力应用,而非教条式照搬。每项原则都有其特定动机,也对应一种过度使用的反模式。
适用场景
- 代码评审中关注点在于结构、职责或耦合时。
- 组件设计讨论:应否拆成一个类还是两个类;某个接口是否应当拆分。
- 新人入职讲解:解释许多 GoF 模式背后的底层原则。
- 依赖方向至关重要的架构讨论(DIP 是六边形架构的基础)。
不适用场景
- 针对单个坏味道的战术性修复——此时按模式的技能(gof-*、ddd-anti-patterns)更具体。
- 教条式地当作检查表使用(“这是否违反任何 SOLID 原则?”)——这正是 SOLID 沦为反模式的方式。每项原则是方向,不是二元判断。
核心内容
S — 单一职责原则:“一个类应当只有一个变更原因。”Bob 大叔的注解是:“职责 = 变更原因”,而非“职责 = 任务”。该原则关注变更轴——不同干系人不应当因无关原因而修改同一个类。过度应用:每个方法一个类,文件散落各处,无收益的间接层。
O — 开闭原则:对扩展开放,对修改关闭——通过新代码(子类、策略、插件)添加新行为,而不修改已有且经过测试的代码。通过针对变化轴的抽象来实现(参见 gof-strategy、gof-template-method)。过度应用:每个类都抽象,“以防将来需要扩展”——过早的灵活性。
L — 里氏替换原则:子类型必须能够替换其基类型而不破坏调用方。如果 Square 继承 Rectangle 并破坏了 setWidth(w); setHeight(h) 的假设,那么即使两种类型都有 width 和 height,这个继承也是错误的。它常被那些并非真正行为子类型的“is-a”继承关系所违反。
I — 接口隔离原则:客户端不应被强迫依赖它们不使用的方法。多个小而聚焦的接口优于一个臃肿接口,后者迫使每个实现者处理它不关心的方法。过度应用:每个方法一个接口,无收益的摩擦。
D — 依赖倒置原则:高层模块不依赖低层模块,二者都依赖抽象。抽象不依赖细节;细节依赖抽象。这是六边形架构(arch-hexagonal)背后的基础思想——领域拥有接口,基础设施实现它。
SOLID 被误用的地方
- 当作二元通过/失败,而非方向。
- 当作过度抽象的借口(“这里需要一个接口,以防以后更换实现”——过早)。
- 当作唯一标准:代码可以满足 SOLID 却依然不可读、缓慢或错误。
- 当作招聘筛选:“你懂 SOLID 吗”把它缩减为词汇检查,忽略了让这些原则有用的判断力。
决策启发式
SOLID 是谈论耦合、内聚和变更的词汇。这些原则是默认值,不是规则。应结合具体代码加以判断。DIP 在架构规模上影响最大(六边形、整洁架构);其余原则在类/模块规模上起作用。LSP 违规是缺陷(被替换的子类型破坏调用方);其他 SOLID 违规是设计坏味道——不那么紧急,但在代价低时仍值得修复。不要为了 SOLID 而引入接口,间接层的代价是真实的,等到第二个实现出现(或确实迫在眉睫)时再引入。
反模式
- SOLID 表演:添加抽象、拆分类、引入接口——全都为了“满足 SOLID”——却不改变代码实际行为。有模式之名而无其实。
- 一个类一个方法:过度激进地拆分类,使代码库变成由细微间接层构成的迷宫。SRP 被夸大超出实用范围。
- 处处接口:每个类都放在恰好只有一个实现的接口之后。ISP 过度应用。
- 为复用而继承:为了复用基类 30% 的方法而子类化它,破坏了 LSP,因为子类并不真正像基类那样行为。
- 把 SOLID 当检查表:“这个 PR 是否违反 SOLID?”忽略了违规是否重要的问题。有些重要;很多并不重要。
参见
- arch-hexagonal —— DIP 在架构规模上的应用。
- arch-layered —— SRP 在层规模上的应用(每层一个职责)。
- gof-strategy、gof-template-method —— OCP 通过具体模式实现。
- gof-review —— SOLID 违规常以 gof-review 中被误用的模式坏味道形式浮现。
参考资料
- Robert C. Martin, Clean Architecture (Pearson, 2017) —— 第 7–11 章涵盖 SOLID。
- Robert C. Martin, Agile Software Development, Principles, Patterns, and Practices (2002) —— 更早的 SOLID 论述。