SOLID——简单易懂

2025-05-27

SOLID——简单易懂

嗨!你最近怎么样?还好吗?希望你还好!

今天我要聊一个大家都在讨论或写作的主题。但有时理解每个原则都很困难。我说的就是 SOLID。

当我问起 SOLID 时,很多人大概总是会想起第一原则(单一职责原则)。但当我问起另一个原则时,有些人却记不住,或者觉得难以解释。我理解。

确实,如果不编写代码或重复每个原则的定义,解释起来确实很困难。但在本文中,我想以一种简单的方式呈现每个原则。因此,我将使用 Typescript 来举例说明。

那么让我们开始吧!

单一职责原则 - SRP

更容易理解和记住的原则。
当我们在编码时,很容易识别我们是否忘记了该原则。

假设我们有一个 TaskManager 类:

class TaskManager {
  constructor() {}
  connectAPI(): void {}
  createTask(): void {
    console.log("Create Task");
  }
  updateTask(): void {
    console.log("Update Task");
  }
  removeTask(): void {
    console.log("Remove Task");
  }
  sendNotification(): void {
    console.log("Send Notification");
  }
  sendReport(): void {
    console.log("Send Report");
  }
}
Enter fullscreen mode Exit fullscreen mode

好吧!你可能注意到了这个问题,不是吗?
TaskManager 类有很多不属于它自己的职责。例如:sendNotification 和 sendReport 方法。

现在,让我们重新实施并应用该解决方案:

class APIConnector {
  constructor() {}
  connectAPI(): void {}
}

class Report {
  constructor() {}
  sendReport(): void {
    console.log("Send Report");
  }
}

class Notificator {
  constructor() {}
  sendNotification(): void {
    console.log("Send Notification");
  }
}

class TaskManager {
  constructor() {}
  createTask(): void {
    console.log("Create Task");
  }
  updateTask(): void {
    console.log("Update Task");
  }
  removeTask(): void {
    console.log("Remove Task");
  }
}
Enter fullscreen mode Exit fullscreen mode

很简单,不是吗?我们只需将通知和报告分离到指定的类中即可。这样我们就遵守了“单一原则职责”!

定义:Each class must have one, and only one, reason to change

开放封闭原则-OCP

第二个原则。我认为也很容易理解。给你一个提示:如果你注意到在某个方法中有很多条件需要验证,那么你很可能就遇到了 OCP 的情况。

让我们想象一下以下考试类的例子:

type ExamType = {
  type: "BLOOD" | "XRay";
};

class ExamApprove {
  constructor() {}
  approveRequestExam(exam: ExamType): void {
    if (exam.type === "BLOOD") {
      if (this.verifyConditionsBlood(exam)) {
        console.log("Blood Exam Approved");
      }
    } else if (exam.type === "XRay") {
      if (this.verifyConditionsXRay(exam)) {
        console.log("XRay Exam Approved!");
      }
    }
  }

  verifyConditionsBlood(exam: ExamType): boolean {
    return true;
  }
  verifyConditionsXRay(exam: ExamType): boolean {
    return false;
  }
}
Enter fullscreen mode Exit fullscreen mode

是的,你可能已经看过这段代码好几次了。首先,我们违反了第一原则 SRP,并设置了很多条件。

现在想象一下,如果出现另一种检查方式,比如超声波检查,我们需要增加另一种验证方法和另一种条件。

让我们重新审视一下这段代码:

type ExamType = {
  type: "BLOOD" | "XRay";
};

interface ExamApprove {
  approveRequestExam(exam: NewExamType): void;
  verifyConditionExam(exam: NewExamType): boolean;
}

class BloodExamApprove implements ExamApprove {
  approveRequestExam(exam: ExamApprove): void {
    if (this.verifyConditionExam(exam)) {
      console.log("Blood Exam Approved");
    }
  }
  verifyConditionExam(exam: ExamApprove): boolean {
    return true;
  }
}

class RayXExamApprove implements ExamApprove {
  approveRequestExam(exam: ExamApprove): void {
    if (this.verifyConditionExam(exam)) {
      console.log("RayX Exam Approved");
    }
  }
  verifyConditionExam(exam: NewExamType): boolean {
    return true;
  }
}
Enter fullscreen mode Exit fullscreen mode

哇,好多了!现在,如果出现其他类型的考试,我们只需实现接口即可ExamApprove。如果出现其他类型的考试验证,我们也只需更新接口即可。

定义:Software entities (such as classes and methods) must be open for extension but closed for modification

里氏替换原则 - LSP

这是比较难理解和解释的例子之一。不过,正如我之前所说,我会让你更容易理解。

假设你有一所大学和两种类型的学生:学生和研究生。

class Student {
  constructor(public name: string) {}

  study(): void {
    console.log(`${this.name} is studying`);
  }

  deliverTCC() {
    /** Problem: Post graduate Students don't delivery TCC */
  }
}

class PostgraduateStudent extends Student {
  study(): void {
    console.log(`${this.name} is studying and searching`);
  }
}
Enter fullscreen mode Exit fullscreen mode

我们这里有个问题,我们正在延长学生的学业,但研究生不需要提交TCC。他只需要学习和搜索。

那我们该如何解决这个问题呢?很简单!我们创建一个 Student 类,并将毕业学生和毕业后学生区分开来:

class Student {
  constructor(public name: string) {}

  study(): void {
    console.log(`${this.name} is studying`);
  }
}

class StudentGraduation extends Student {
  study(): void {
    console.log(`${this.name} is studying`);
  }

  deliverTCC() {}
}

class StudentPosGraduation extends Student {
  study(): void {
    console.log(`${this.name} is studying and searching`);
  }
}
Enter fullscreen mode Exit fullscreen mode

现在我们有了更好的方法来分离各自的职责。这个原则的名字可能有点吓人,但其原理其实很简单。

定义:Derived classes (or child classes) must be able to replace their base classes (or parent classes)

接口隔离原则 - ISP

要理解这个原则,诀窍在于记住定义。不应该强迫一个类去实现那些不会用到的方法。

想象一下,你有一个类实现一个永远不会被使用的接口。

假设有一家商店,有一位销售员和一位接待员。销售员和接待员都有工资,但只有销售员有佣金。

让我们看看问题所在:

interface Employee {
  salary(): number;
  generateCommission(): void;
}

class Seller implements Employee {
  salary(): number {
    return 1000;
  }
  generateCommission(): void {
    console.log("Generating Commission");
  }
}

class Receptionist implements Employee {
  salary(): number {
    return 1000;
  }
  generateCommission(): void {
    /** Problem: Receptionist don't have commission  */
  }
}
Enter fullscreen mode Exit fullscreen mode

两者都实现了 Employee 接口,但是接待员没有佣金。所以我们被迫实现一个永远不会用到的方法。

所以解决方案是:

interface Employee {
  salary(): number;
}

interface Commissionable {
  generateCommission(): void;
}

class Seller implements Employee, Commissionable {
  salary(): number {
    return 1000;
  }

  generateCommission(): void {
    console.log("Generating Commission");
  }
}

class Receptionist implements Employee {
  salary(): number {
    return 1000;
  }
}
Enter fullscreen mode Exit fullscreen mode

非常简单!现在我们有两个接口了!雇主类和佣金接口。现在只有卖家类会实现这两个用于佣金的接口。接待员类不需要只实现员工类。所以接待员类不必被迫实现那些永远不会用到的方法。

定义:A class should not be forced to implement interfaces and methods that will not be used.

依赖倒置原则-DIP

最后一个!听名字你可能觉得它很难记!但你可能每次都见过这个原则。

假设你有一个 Service 类,它与一个 Repository 类集成,后者会调用数据库,例如 Postgress。但是,如果 Repository 类发生变化,数据库也变为 MongoDB 的话,情况就会不同。

让我们来看一个例子:

interface Order {
  id: number;
  name: string;
}

class OrderRepository {
  constructor() {}
  saveOrder(order: Order) {}
}

class OrderService {
  private orderRepository: OrderRepository;

  constructor() {
    this.orderRepository = new OrderRepository();
  }

  processOrder(order: Order) {
    this.orderRepository.saveOrder(order);
  }
}
Enter fullscreen mode Exit fullscreen mode

我们注意到,存储库 OrderService 类直接与 OrderRepository 类的具体实现耦合。

让我们重新回顾一下这个例子:

interface Order {
  id: number;
  name: string;
}

class OrderRepository {
  constructor() {}
  saveOrder(order: Order) {}
}

class OrderService {
  private orderRepository: OrderRepository;

  constructor(repository: OrderRepository) {
    this.orderRepository = repository;
  }

  processOrder(order: Order) {
    this.orderRepository.saveOrder(order);
  }
}
Enter fullscreen mode Exit fullscreen mode

太棒了!好多了!现在我们在构造函数中接收存储库作为参数,以便实例化和使用。现在我们依赖于抽象,并且不需要知道我们正在使用哪个存储库。

定义:depend on abstractions rather than concrete implementations

精加工

那么你现在感觉如何?我希望通过这些简单的例子,你能记住并理解在代码中使用这些原则的原因和含义。这不仅能让你写出简洁的代码,还能让你更容易理解和扩展。

希望你喜欢!
非常感谢,祝你一切安好!

联系方式:
Linkedin:https://www.linkedin.com/in/kevin-uehara/
Instagram:https://www.instagram.com/uehara_kevin/
Twitter:https: //twitter.com/ueharaDev
Github:https: //github.com/kevinuehara dev.to:https://dev.to/kevin-uehara
Youtube : https: //www.youtube.com/@ueharakevin/

文章来源:https://dev.to/kevin-uehara/solid-the-simple-way-to-understand-47im
PREV
简历中十大 React 项目
NEXT
我在一家梦想公司担任专业开发人员的头四年。以及我离开的原因。