设计原则:SOLID 与高内聚低耦合
Order 既算价格、又写数据库、还发邮件、顺便打日志——改邮件模板要动订单类,改价格规则也要动它,测试时想只测「算价」却不得不连 DB 一起起。反过来,一个类只做一类事、对扩展开放对修改关闭、子类能安全替换父类、接口别逼实现方接用不到的方法、依赖抽象不依赖具体,改一处就只动一处,加新功能多半是「加新类」而不是「改老类」。这一套就是 SOLID 与 高内聚低耦合:本章把五条原则和实践含义讲清楚,再说说原则与取舍。
一、SOLID 五原则
SOLID 是五个面向对象设计原则的首字母缩写,用来指导「类与模块该怎么拆、怎么依赖」,让代码更易改、易测、易扩展。
1. S — 单一职责(SRP, Single Responsibility Principle)
一个类只有一个引起它变化的原因,或者说只负责一类事。若「算价格」和「发邮件」都在一个类里,改价格规则和改邮件模板都会改这个类,职责就混了。拆成 PricingService 和 NotificationService,各自只为一类变化负责,修改和测试都更清晰。
2. O — 开闭(OCP, Open-Closed Principle)
对扩展开放,对修改关闭。加新行为时,尽量通过「扩展」(新子类、新实现、新策略)而不是「改已有类」。例如支持新的支付方式:不要往老类里堆 if-else,而是新增一个「支付方式」的实现类,调用方依赖抽象接口,这样老代码不用动,符合开闭。
3. L — 里氏替换(LSP, Liskov Substitution Principle)
子类可以替换父类使用,且不破坏调用方的预期。即:凡是依赖「父类型」的地方,换成子类型后行为仍然合理,不出现子类「悄悄改契约」的情况(例如父类说「返回值≥0」,子类却可能返回负值)。设计继承时要保证子类不削弱前置条件、不强化后置条件。
4. I — 接口隔离(ISP, Interface Segregation Principle)
客户端不应被迫依赖它用不到的接口。若一个接口很大,实现类必须实现所有方法,但调用方只用到其中一小部分,就会产生「 fat interface 」和多余依赖。应拆成多个小接口,按需依赖,这样实现类也只实现真正需要的方法。
5. D — 依赖倒置(DIP, Dependency Inversion Principle)
高层模块不依赖低层模块,二者都依赖抽象;抽象不依赖细节,细节依赖抽象。例如业务层不要直接 new 一个具体的数据库实现,而是依赖「仓储接口」或「数据访问接口」,由外部注入具体实现(如测试时注入 mock)。这样高层逻辑稳定,换存储、换实现只需换注入对象。
二、高内聚、低耦合的实践含义
高内聚:同一模块/类内部,职责相关、共同完成一个清晰目标,而不是东一块西一块。例如「订单计算」模块里都是和价格、折扣、税费相关的逻辑,这就是内聚高。低耦合:模块之间依赖少、接口小且稳定,改一个模块尽量不牵动其他模块。例如业务层只通过「仓储接口」的几个方法访问数据,而不是直接依赖表结构或 SQL,耦合就低。
高内聚低耦合和 SOLID 是同一方向:SRP、ISP 促进内聚与接口精简;DIP、OCP 降低耦合、便于扩展。实践中:按职责拆类(内聚)、通过接口交互、依赖抽象(耦合)、避免跨层跨模块的直接依赖。
三、原则与取舍
SOLID 和高内聚低耦合是指导原则,不是教条。实践中要权衡:
- 规模与阶段:小脚本、一次性工具不必强行拆成五层;长期维护、多人协作的系统才更需要严格分层与 SOLID。
- 过度拆分:每个类只一个方法也会导致碎片化、调用链过长。职责要「单一」但也要「合理粒度」。
- 抽象成本:为「可能未来会换」加一层抽象,若长期只有一种实现,可能只是多了一层间接。抽象在「确实有多实现或要测试隔离」时价值最大。
- 团队共识:原则要落地成团队约定和代码评审标准,否则容易各写各的,架构漂移。
建议:先写能工作的简单实现,再在「改不动、测不动、扩展疼」的地方按 SOLID 与内聚耦合来重构,而不是一上来就为所有可能扩展点加抽象。
一句话: SOLID 五原则——S 单一职责、O 开闭、L 里氏替换、I 接口隔离、D 依赖倒置——指导我们拆类、依赖抽象、便于扩展与测试。高内聚是模块内职责相关,低耦合是模块间依赖少而稳。原则要与项目规模和阶段做取舍,在「改不动、测不动」处优先应用。
四、小结
SOLID:单一职责(一类一事)、开闭(扩展开放修改关闭)、里氏替换(子类可替换父类)、接口隔离(小接口、按需依赖)、依赖倒置(依赖抽象)。高内聚是模块内职责集中,低耦合是模块间依赖少而清晰。原则需结合项目规模与阶段做取舍,在痛点处重构比一开始过度设计更稳妥。下一章进入设计模式入门:创建型、结构型、行为型概览,以及何时引入、何时避免过度设计。