面向对象设计的七大核心原则

我将用简洁易懂的方式介绍面向对象设计的七大核心原则(SOLID + 合成复用/迪米特法则),通过定义 + 实际案例 + 代码对比帮助理解它们的核心思想。


1. 单一职责原则 (SRP - Single Responsibility Principle)

定义:一个类只应有一个引起它变化的原因
核心思想:高内聚、低耦合
违反案例

1
2
3
4
5
// 糟糕设计:一个类同时处理订单逻辑和发送邮件
class OrderService {
public void createOrder() { /* 订单创建逻辑 */ }
public void sendEmail() { /* 邮件发送逻辑 */ }
}

正确实现

1
2
3
4
5
6
7
8
// 拆分职责
class OrderService {
public void createOrder() { /* 仅处理订单 */ }
}

class EmailService {
public void sendEmail() { /* 仅处理邮件 */ }
}

实际意义:修改邮件发送方式时,不会影响订单核心逻辑


2. 开闭原则 (OCP - Open-Closed Principle)

定义:软件实体应对扩展开放,对修改关闭
核心思想:通过抽象和继承/组合实现扩展
违反案例

1
2
3
4
5
6
7
8
// 硬编码折扣类型,新增类型需修改源码
class DiscountCalculator {
public double calculate(String type) {
if ("VIP".equals(type)) return 0.8;
else if ("NEW_USER".equals(type)) return 0.9;
// 新增类型必须修改此处
}
}

正确实现

1
2
3
4
5
6
7
8
9
10
11
12
13
// 抽象折扣策略
interface DiscountStrategy {
double applyDiscount();
}

class VIPDiscount implements DiscountStrategy {
public double applyDiscount() { return 0.8; }
}

// 新增折扣类型只需添加新类
class FestivalDiscount implements DiscountStrategy {
public double applyDiscount() { return 0.7; }
}

实际意义:系统扩展时无需修改已有稳定代码,降低风险


3. 里氏替换原则 (LSP - Liskov Substitution Principle)

定义:子类必须能够替换父类且不破坏程序逻辑
解释: 任何基类可以出现的地方,子类一定可以出现。里氏替换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏替换原则是对实现抽象化的具体步骤的规范。
核心思想:继承关系应确保行为兼容性
违反案例

1
2
3
4
5
6
7
8
9
10
11
12
13
class Rectangle {
protected int width, height;
public void setWidth(int w) { width = w; }
public void setHeight(int h) { height = h; }
}

// 正方形不是长方形的子类型!
class Square extends Rectangle {
public void setWidth(int w) {
width = w;
height = w; // 破坏父类行为
}
}

正确实现

1
2
3
4
5
6
7
8
// 通过接口隔离形状类型
interface Shape {
double getArea();
}

class Rectangle implements Shape { /* 实现长方形逻辑 */ }

class Square implements Shape { /* 独立实现正方形 */ }

实际意义:避免因继承导致不可预知的错误


4. 接口隔离原则 (ISP - Interface Segregation Principle)

定义:客户端不应被迫依赖它不使用的接口
核心思想:接口粒度细化,避免“胖接口”,使用多个隔离的接口,比使用单个接口要好
违反案例

1
2
3
4
5
6
7
8
9
10
11
12
// 臃肿的 Worker 接口
interface Worker {
void work();
void eat();
void sleep();
}

// 机器人被迫实现无关方法
class Robot implements Worker {
void work() { /*...*/ }
void eat() { throw new UnsupportedOperationException(); } // 违反原则!
}

正确实现

1
2
3
4
5
6
7
8
9
10
11
// 拆分接口
interface Workable {
void work();
}

interface Eatable {
void eat();
}

class Human implements Workable, Eatable { /* 实现全部 */ }
class Robot implements Workable { /* 只需实现工作 */ }

实际意义:减少不必要的依赖,提高系统灵活性


5. 依赖倒置原则 (DIP - Dependency Inversion Principle)

定义:高层模块不应依赖低层模块,二者都应依赖抽象(开闭原则的基础)
核心思想:面向接口编程,解耦模块关系
违反案例

1
2
3
4
5
6
7
// 高层直接依赖具体数据库
class ReportService {
private MySQLDatabase db; // 紧耦合
public void generateReport() {
db.query("SELECT ...");
}
}

正确实现

1
2
3
4
5
6
7
8
9
10
11
12
13
// 依赖抽象
interface Database {
Result query(String sql);
}

class ReportService {
private Database db; // 依赖接口
public ReportService(Database db) { this.db = db; }
}

// 可轻松切换数据库类型
class MySQLDatabase implements Database { /* 实现 */ }
class MongoDB implements Database { /* 实现 */ }

实际意义:提升代码可测试性和可维护性


6. 合成复用原则 (CRP - Composite Reuse Principle)

定义:优先使用对象组合,而不是继承来达到复用目的
核心思想:通过“黑盒复用”降低耦合度
案例对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 错误:通过继承复用
class Engine { /* 发动机功能 */ }

class Car extends Engine {
// 汽车不是发动机的特化,继承关系错误!
}

// 正确:通过组合复用
class Car {
private Engine engine;
public Car(Engine engine) {
this.engine = engine;
}
}

实际意义:避免继承层次过深导致的“脆弱的基类”问题


7. 迪米特法则 (LoD - Law of Demeter)

定义:一个对象应尽可能少地了解其他对象
核心思想:降低类之间的耦合度
违反案例

1
2
3
4
5
6
7
8
class Order {
private Customer customer;
public Customer getCustomer() { return customer; }
}

// 高层模块过度深入调用
Order order = getOrder();
String city = order.getCustomer().getAddress().getCity(); // 链式调用违反法则

正确实现

1
2
3
4
5
6
7
8
9
10
// 在Order中封装所需信息
class Order {
private Customer customer;
public String getCustomerCity() {
return customer.getAddress().getCity();
}
}

// 调用方只需与Order交互
String city = order.getCustomerCity();

实际意义:减少依赖链断裂风险,提高代码健壮性


总结:七大原则的核心理念

原则 关键目标 一句话记忆
SRP 功能聚焦 一个类只做一件事
OCP 易于扩展 扩展不修改旧代码
LSP 继承安全 子类能顶替父类
ISP 接口精简 不用就别强迫我
DIP 解耦依赖 依赖抽象不依赖具体
CRP 灵活复用 组合优于继承
LoD 减少知晓 你的事我不多问

实际应用技巧:当发现以下代码症状时,回顾对应原则:

  • 频繁修改某个类 → 违反SRP/OCP
  • 子类方法抛出UnsupportedOperationException → 违反LSP
  • 接口有大量空实现 → 违反ISP
  • 单元测试难写 → 违反DIP
  • 继承层次超过3层 → 违反CRP
  • 调用链超过两个点(如a.getB().getC()) → 违反LoD