SOLID 是面向对象设计的五大核心原则的首字母缩写,由罗伯特·C·马丁(Robert C. Martin)提出,旨在提升代码的可维护性、扩展性和可测试性。
一、SOLID 原则概览
SOLID 是五个基本原则首字母的缩写。
缩写
英文全称
中文全称
核心思想
SRP
Single Responsibility Principle
单一职责原则
一个类应该只有一个引起变化的原因。
OCP
Open/Closed Principle
开闭原则
软件实体应对扩展开放,对修改关闭。
LSP
Liskov Substitution Principle
里氏替换原则
子类必须能够替换掉它们的父类。
ISP
Interface Segregation Principle
接口隔离原则
不应强迫客户依赖于它们不用的方法。
DIP
Dependency Inversion Principle
依赖倒置原则
依赖于抽象,而非具体实现。
二、详细描述
1. SRP:单一职责原则
英文全称:Single Responsibility Principle
核心思想:一个类(或模块、函数)应仅有一个引起其变化的原因,即只负责一项单一功能。
实例:
假设一个 UserManager类同时负责“用户数据存储”和“用户邮件通知”:
// 违反 SRP:同时处理存储和通知
class UserManager {
public void saveUser(User user) { /* 操作数据库 */ }
public void sendWelcomeEmail(User user) { /* 调用邮件服务 */ }
}
// 遵循 SRP:拆分为两个独立类
class UserRepository {
public void saveUser(User user) { /* 仅负责存储 */ }
}
class UserNotifier {
public void sendWelcomeEmail(User user) { /* 仅负责通知 */ }
}
好处:修改存储逻辑(如换数据库)不会影响通知功能,反之亦然。
2. OCP:开闭原则
英文全称:Open/Closed Principle
核心思想:软件实体(类、模块、函数等)应对扩展开放,对修改关闭。即通过扩展(新增代码)实现功能变化,而非修改已有代码。
实例:
计算不同形状的面积,初始仅支持圆形:
// 违反 OCP:新增形状需修改原有类
class AreaCalculator {
public double calculateArea(Shape shape) {
if (shape instanceof Circle) {
return Math.PI * ((Circle) shape).getRadius() * ((Circle) shape).getRadius();
} else if (shape instanceof Rectangle) { // 新增矩形需修改此处
return ((Rectangle) shape).getWidth() * ((Rectangle) shape).getHeight();
}
throw new IllegalArgumentException("Unsupported shape");
}
}
// 遵循 OCP:通过抽象和多态扩展
interface Shape {
double area(); // 抽象方法
}
class Circle implements Shape {
private double radius;
public double area() { return Math.PI * radius * radius; }
}
class Rectangle implements Shape {
private double width, height;
public double area() { return width * height; }
}
class AreaCalculator {
public double calculateArea(Shape shape) {
return shape.area(); // 无需修改,直接调用抽象方法
}
}
好处:新增三角形(Triangle)时,只需实现 Shape接口,无需修改 AreaCalculator。
3. LSP:里氏替换原则
英文全称:Liskov Substitution Principle
核心思想:所有引用基类(父类)的地方,必须能透明地替换为其子类对象,且不破坏程序的正确性。即子类需严格遵循父类的行为契约。
实例:
正方形不能作为长方形的子类(违反 LSP):
// 父类:长方形
class Rectangle {
protected int width, height;
public void setWidth(int w) { width = w; }
public void setHeight(int h) { height = h; }
public int getArea() { return width * height; }
}
// 子类:正方形(错误继承)
class Square extends Rectangle {
@Override
public void setWidth(int w) {
super.setWidth(w);
super.setHeight(w); // 强制宽高相等,破坏父类行为
}
@Override
public void setHeight(int h) {
super.setWidth(h);
super.setHeight(h);
}
}
// 使用场景:假设某个函数依赖长方形的宽高独立性
void resize(Rectangle rect) {
rect.setWidth(5);
rect.setHeight(4);
assert rect.getArea() == 20; // 若传入 Square,面积会是 25,断言失败!
}
修正:避免让正方形继承长方形,或通过抽象更严格的契约(如定义 WidthHeightIndependent接口)。
4. ISP:接口隔离原则
英文全称:Interface Segregation Principle
核心思想:客户端不应依赖它不需要的接口。即一个接口应只包含客户端需要的方法,避免“胖接口”。
实例:
设计一个动物接口,包含“游泳”和“飞行”方法,但企鹅(会游泳不会飞)和鸵鸟(会飞不会游泳?不,鸵鸟也不会飞)会被迫实现不需要的方法:
// 违反 ISP:胖接口
interface Animal {
void swim();
void fly();
}
class Penguin implements Animal {
public void swim() { /* 会游泳 */ }
public void fly() { throw new UnsupportedOperationException(); } // 强制实现无用方法
}
// 遵循 ISP:拆分为细粒度接口
interface Swimmable { void swim(); }
interface Flyable { void fly(); }
class Penguin implements Swimmable { /* 仅实现需要的方法 */ }
class Eagle implements Flyable, Swimmable { /* 可选实现 */ }
好处:客户端只需依赖自己需要的接口,降低耦合。
5. DIP:依赖倒置原则
英文全称:Dependency Inversion Principle
核心思想:高层模块不应依赖低层模块,二者应依赖抽象;抽象不应依赖细节,细节应依赖抽象。即通过接口或抽象类解耦。
实例:
订单服务直接依赖具体的 MysqlOrderRepository:
// 违反 DIP:高层依赖低层具体实现
class OrderService {
private MysqlOrderRepository repository; // 具体数据库实现
public void createOrder(Order order) {
repository.save(order);
}
}
// 遵循 DIP:依赖抽象接口
interface OrderRepository {
void save(Order order);
}
class MysqlOrderRepository implements OrderRepository { /* 实现 */ }
class RedisOrderRepository implements OrderRepository { /* 另一种实现 */ }
class OrderService {
private OrderRepository repository; // 依赖抽象接口
public OrderService(OrderRepository repo) {
this.repository = repo; // 注入具体实现(如通过构造函数)
}
public void createOrder(Order order) {
repository.save(order); // 无需关心具体数据库
}
}
好处:切换数据库实现(如从 MySQL 到 Redis)时,只需修改注入的 OrderRepository实例,无需改动 OrderService。
三、其他优秀的设计原则
1. DRY:不要重复自己
英文全称:Don’t Repeat Yourself
核心思想:相同功能的代码应只出现一次,避免冗余。重复的代码会增加维护成本(修改时需同步所有副本)。
实例:
多个地方计算用户年龄:
// 违反 DRY:重复逻辑
int age1 = currentDate.getYear() - user1.getBirthday().getYear();
int age2 = currentDate.getYear() - user2.getBirthday().getYear();
// 遵循 DRY:封装为工具方法
public class AgeUtils {
public static int calculateAge(LocalDate birthday, LocalDate current) {
return current.getYear() - birthday.getYear();
}
}
int age1 = AgeUtils.calculateAge(user1.getBirthday(), currentDate);
int age2 = AgeUtils.calculateAge(user2.getBirthday(), currentDate);
2. KISS:保持简单
英文全称:Keep It Simple, Stupid
核心思想:优先选择最简单的解决方案,避免过度设计。复杂的代码更易出错且难以维护。
实例:
实现一个数组求和功能:
// 过度设计:使用复杂流操作(可能没必要)
int sum = Arrays.stream(array).reduce(0, Integer::sum);
// KISS:简单循环更直观
int sum = 0;
for (int num : array) {
sum += num;
}
3. YAGNI:你不需要它
英文全称:You Ain’t Gonna Need It
核心思想:不要提前实现未来可能需要的功能,只在实际需要时编写代码。过度预测需求会导致冗余。
实例:
开发一个博客系统时,有人提议“未来可能需要支持 Markdown 和富文本两种格式”,于是提前实现两套解析器。但实际用户仅用 Markdown:
// YAGNI:提前实现未需求的功能
class Post {
private String markdownContent;
private String richTextContent; // 冗余字段
}
// 正确做法:仅实现当前需要的 Markdown 解析
class Post {
private String markdownContent;
}
// 后续若需要富文本,再扩展
4. 迪米特法则(LoD/LKP):最少知识原则
英文全称:Law of Demeter / Least Knowledge Principle
核心思想:一个对象应仅与直接朋友通信,避免访问“朋友的朋友”。减少对象间的直接依赖,降低耦合。
实例:
用户(User)有一个地址(Address),地址包含城市(City):
// 违反迪米特:User 直接访问 Address 的 City
class User {
private Address address;
public String getCity() {
return address.getCity().getName(); // 链式调用“朋友的朋友”
}
}
// 遵循迪米特:User 仅通过 Address 提供的方法获取城市
class Address {
private City city;
public String getCityName() { // 封装内部细节
return city.getName();
}
}
class User {
private Address address;
public String getCity() {
return address.getCityName(); // 仅与直接朋友 Address 交互
}
}
总结
这些原则共同构成了构建健壮、灵活软件的基础:
-
SRP 和 ISP 关注职责分离和接口设计。
-
OCP、LSP 和 DIP 是面向抽象设计和多态的核心,共同实现了系统的可扩展性和松耦合。
-
LoD、DRY、KISS、YAGNI 则是指导我们编写清晰、可维护代码的实用准则。
原则名称
英文全称
核心思想
单一职责原则
Single Responsibility Principle
一个类仅负责一项功能
开闭原则
Open/Closed Principle
对扩展开放,对修改关闭
里氏替换原则
Liskov Substitution Principle
子类可透明替换父类,不破坏程序逻辑
接口隔离原则
Interface Segregation Principle
客户端不依赖不需要的接口
依赖倒置原则
Dependency Inversion Principle
高层与低层依赖抽象,而非具体实现
不要重复自己
Don’t Repeat Yourself (DRY)
相同功能代码只出现一次
保持简单
Keep It Simple (KISS)
优先简单方案,避免过度设计
你不需要它
You Ain’t Gonna Need It (YAGNI)
不提前实现未需求的功能
迪米特法则
Law of Demeter (LoD)
减少对象间直接交互,仅与“直接朋友”通信
欢迎大家一起整理更好的设计原则,帮助实现代码的节能减碳。