SOLID面向对象设计原则Skill solid-principles

SOLID原则是面向对象编程和设计的五个基本原则,包括单一职责、开闭原则、里氏替换、接口隔离和依赖倒置。这些原则帮助开发者构建可维护、可扩展、可测试的软件系统,提高代码质量,降低耦合度,增强系统灵活性。适用于软件架构设计、代码重构、系统维护等场景。关键词:SOLID原则、面向对象设计、软件架构、代码重构、可维护性、设计模式、编程最佳实践、软件工程

架构设计 2 次安装 44 次浏览 更新于 3/2/2026

name: solid-principles description: SOLID面向对象设计原则,用于构建可维护的代码

SOLID原则

面向对象设计的五个原则,可构建可维护、可扩展的软件。

S - 单一职责原则

一个类应该只有一个引起变化的原因。

// 错误 - 多个职责
class UserService {
  createUser(data: UserData) { /* ... */ }
  sendEmail(user: User, message: string) { /* ... */ }
  generateReport(users: User[]) { /* ... */ }
  validateEmail(email: string) { /* ... */ }
}

// 正确 - 每个类单一职责
class UserService {
  constructor(
    private repository: UserRepository,
    private validator: UserValidator
  ) {}

  createUser(data: UserData): User {
    this.validator.validate(data);
    return this.repository.save(data);
  }
}

class EmailService {
  send(to: string, message: string) { /* ... */ }
}

class UserReportGenerator {
  generate(users: User[]): Report { /* ... */ }
}

class EmailValidator {
  validate(email: string): boolean { /* ... */ }
}

适用时机: 如果用"和"来描述一个类(UserService创建用户和发送邮件和…),就拆分它。

O - 开闭原则

对扩展开放,对修改关闭。

// 错误 - 必须修改类来添加新类型
class PaymentProcessor {
  process(payment: Payment) {
    if (payment.type === 'credit') {
      // 处理信用卡
    } else if (payment.type === 'paypal') {
      // 处理PayPal
    } else if (payment.type === 'crypto') {
      // 处理加密货币 - 必须修改!
    }
  }
}

// 正确 - 无需修改即可扩展
interface PaymentMethod {
  process(amount: number): Promise<Receipt>;
}

class CreditCardPayment implements PaymentMethod {
  async process(amount: number): Promise<Receipt> {
    // 信用卡逻辑
  }
}

class PayPalPayment implements PaymentMethod {
  async process(amount: number): Promise<Receipt> {
    // PayPal逻辑
  }
}

// 添加加密货币支付无需修改现有代码
class CryptoPayment implements PaymentMethod {
  async process(amount: number): Promise<Receipt> {
    // 加密货币逻辑
  }
}

class PaymentProcessor {
  process(method: PaymentMethod, amount: number) {
    return method.process(amount);
  }
}

适用时机: 当添加新功能需要修改现有已测试的代码时。

L - 里氏替换原则

子类型必须能够替换其基类型。

// 错误 - Square违反了Rectangle的契约
class Rectangle {
  constructor(protected width: number, protected height: number) {}

  setWidth(width: number) { this.width = width; }
  setHeight(height: number) { this.height = height; }
  getArea() { return this.width * this.height; }
}

class Square extends Rectangle {
  setWidth(width: number) {
    this.width = width;
    this.height = width; // 违反预期!
  }
  setHeight(height: number) {
    this.width = height;
    this.height = height; // 违反预期!
  }
}

// 这会出错:
function doubleWidth(rect: Rectangle) {
  const originalHeight = rect.getArea() / rect.width;
  rect.setWidth(rect.width * 2);
  // 对于Square,高度也被加倍 - 意料之外!
}

// 正确 - 分离的层次结构
interface Shape {
  getArea(): number;
}

class Rectangle implements Shape {
  constructor(private width: number, private height: number) {}
  getArea() { return this.width * this.height; }
}

class Square implements Shape {
  constructor(private side: number) {}
  getArea() { return this.side * this.side; }
}

适用时机: 如果子类重写改变了调用者依赖的行为。

I - 接口隔离原则

客户端不应该依赖它不使用的接口。

// 错误 - 臃肿的接口
interface Worker {
  work(): void;
  eat(): void;
  sleep(): void;
  attendMeeting(): void;
  writeReport(): void;
}

class Robot implements Worker {
  work() { /* ... */ }
  eat() { throw new Error('机器人不需要吃饭'); }  // 被迫实现!
  sleep() { throw new Error('机器人不需要睡觉'); }
  attendMeeting() { throw new Error('不适用'); }
  writeReport() { throw new Error('不适用'); }
}

// 正确 - 隔离的接口
interface Workable {
  work(): void;
}

interface Feedable {
  eat(): void;
}

interface Sleepable {
  sleep(): void;
}

interface MeetingAttendee {
  attendMeeting(): void;
}

class Human implements Workable, Feedable, Sleepable, MeetingAttendee {
  work() { /* ... */ }
  eat() { /* ... */ }
  sleep() { /* ... */ }
  attendMeeting() { /* ... */ }
}

class Robot implements Workable {
  work() { /* ... */ }
}

适用时机: 当类实现不需要的方法,或抛出"未实现"错误时。

D - 依赖倒置原则

依赖抽象,而不是具体实现。

// 错误 - 高层依赖低层
class MySQLDatabase {
  query(sql: string) { /* ... */ }
}

class UserRepository {
  private db = new MySQLDatabase();  // 紧耦合!

  findById(id: string) {
    return this.db.query(`SELECT * FROM users WHERE id = '${id}'`);
  }
}

// 正确 - 都依赖抽象
interface Database {
  query<T>(sql: string): Promise<T>;
}

class MySQLDatabase implements Database {
  async query<T>(sql: string): Promise<T> { /* ... */ }
}

class PostgreSQLDatabase implements Database {
  async query<T>(sql: string): Promise<T> { /* ... */ }
}

class UserRepository {
  constructor(private db: Database) {}  // 依赖注入!

  findById(id: string) {
    return this.db.query(`SELECT * FROM users WHERE id = '${id}'`);
  }
}

// 轻松切换实现
const repo = new UserRepository(new PostgreSQLDatabase());

适用时机: 当测试困难,或修改一个模块会破坏其他模块时。

SOLID实践

识别违反情况

原则 代码异味
SRP 类有许多不相关的方法
OCP 添加功能需要修改现有代码
LSP 子类抛出"不支持"或行为不同
ISP 类实现它不使用的方法
DIP new关键字散落在业务逻辑中

应用SOLID

  1. 从简单开始 - 不要一开始就过度设计
  2. 需要时重构 - 感受到痛点时再应用
  3. 使用依赖注入 - 使DIP自然实现
  4. 优先组合 - 而非继承(有助于LSP)
  5. 编写小接口 - 比后来拆分更容易

平衡

SOLID是指导,不是法律。过度应用会导致:

  • 太多微小类
  • 难以理解的间接层
  • 目前没人需要的抽象

在以下情况应用SOLID:

  • 代码难以测试
  • 变化在代码库中产生连锁反应
  • 多个地方需要类似修改
  • 正在添加某个东西的第3个变体

检查清单

  • [ ] 每个类是否有单一、明确的目的?
  • [ ] 能否在不修改现有代码的情况下添加功能?
  • [ ] 子类能否安全替换父类?
  • [ ] 接口是否专注且最小化?
  • [ ] 依赖是否注入,而不是内部创建?