SOLID 是面向对象设计的五大基本原则的首字母缩写,五大原则由 Robert C. Martin 在《敏捷软件开发》中提出,SOLID 缩写则由 Michael Feathers 命名。这五个原则是设计模式的理论基石,几乎所有 GoF 23 种模式都可以视为 SOLID 原则在特定场景下的具体应用。
本篇仅阐述各原则的内涵,不展开代码示例。
定义:一个类应该只有一个引起它变化的原因(A class should have only one reason to change)。
这个原则的本质不是"一个类只做一件事",而是"一个类的所有方法应该服务于同一个行为域,受同一个变化源驱动"。当多个不相关的职责汇聚在一个类中时,任何一个职责的变化都可能导致该类被修改,从而增加引入缺陷的风险。
违反原则的表现:一个类同时承担多个不相关职责,例如某个类既负责认证又负责用户信息管理。认证策略的变化和用户信息结构的变化都会导致该类被修改。
重构方式:将不同职责拆分为独立的类(如认证服务、用户信息服务、密码哈希服务),各自只受一个变化源驱动。
定义:软件实体(类、模块、函数)应该对扩展开放,对修改关闭(Software entities should be open for extension, but closed for modification)。
"对修改关闭"意味着一旦模块开发完毕,其源代码不应再被修改;"对扩展开放"意味着可以通过添加新代码来扩展模块的行为。这两个看似矛盾的目标通过抽象来实现:定义稳定的抽象接口,通过新的实现类来扩展行为,而非修改已有代码。
违反原则的表现:用大量 switch-case 根据类型分支处理不同行为,每新增一种类型都要修改此 switch 和联合类型。
重构方式:定义策略接口,每种类型一个实现类;新增类型只需新增一个策略实现,业务类代码无需修改。
定义:子类型必须能够替换其基类型,而不影响程序的正确性(Subtypes must be substitutable for their base types)。
LSP 是继承关系的约束条件。它要求子类不能破坏基类建立的行为契约:基类的前置条件子类不能加强,基类的后置条件子类不能削弱,基类的不变式子类必须保持。经典的正方形-矩形反例正是违反了这一约束。
违反原则的表现:正方形继承矩形并强制"宽高相等",这加强了基类的前置条件,导致基类可用的操作在子类中不再安全。
重构方式:不要强行建立错误的继承关系,而是提取共同抽象(如"四边形"接口),让矩形和正方形作为平等的实现。关键洞察:正方形不是矩形的子类型。
定义:客户端不应被迫依赖它不使用的方法(Clients should not be forced to depend on methods they do not use)。
ISP 的核心思想是接口的粒度应该足够细。当一个接口包含过多方法时,不同客户端可能只需要其中的一部分,却被迫实现或依赖全部方法。这种"胖接口"不仅增加了实现者的负担,还引入了不必要的耦合。
违反原则的表现:定义一个"万能设备"胖接口,智能灯泡被迫实现拍照、打电话等它根本不支持的能力,只能抛异常或空实现。
重构方式:将胖接口拆分为细粒度的能力接口(可开关、可充电、可连 WiFi、可播放音频等),每个设备只实现它真正需要的能力,客户端只依赖自己需要的接口。
定义:高层模块不应依赖低层模块,两者都应依赖抽象;抽象不应依赖细节,细节应依赖抽象(High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions)。
DIP 是控制反转(IoC)和依赖注入(DI)的理论基础。传统分层架构中,高层模块直接依赖低层模块的具体实现,导致低层实现的变化会波及高层。DIP 通过引入抽象层,使高层和低层都依赖这个稳定的抽象,从而解耦两者。
违反原则的表现:高层业务类直接 new 具体低层模块(如直接在业务类中实例化 EmailNotificationService、SMSNotificationService)。
重构方式:定义通知服务抽象接口,高层模块只依赖抽象,低层模块实现抽象;通过构造注入依赖,新增通知渠道只需新增一个适配器实现,高层模块无需修改。
SOLID 五原则从不同维度约束了面向对象设计的质量:
| 原则 | 核心关注点 | 目标 |
|---|---|---|
| 单一职责(SRP) | 一个类只因一个原因改变 | 降低类复杂度、提高内聚 |
| 开放封闭(OCP) | 对扩展开放、对修改关闭 | 提升可扩展性 |
| 里氏替换(LSP) | 子类可替换基类而不破坏契约 | 保障继承的正确性 |
| 接口隔离(ISP) | 客户端不依赖不使用的方法 | 降低耦合 |
| 依赖倒置(DIP) | 高层与低层都依赖抽象 | 解耦、支持 IoC/DI |
这五者相互关联:DIP 依赖抽象为 OCP 提供实现手段,LSP 保证抽象替换的安全,ISP 让接口粒度合适,SRP 让每个类职责单一。掌握 SOLID 是理解设计模式的必要前提。