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");
}
}
好吧!你可能注意到了这个问题,不是吗?
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");
}
}
很简单,不是吗?我们只需将通知和报告分离到指定的类中即可。这样我们就遵守了“单一原则职责”!
定义: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;
}
}
是的,你可能已经看过这段代码好几次了。首先,我们违反了第一原则 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;
}
}
哇,好多了!现在,如果出现其他类型的考试,我们只需实现接口即可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`);
}
}
我们这里有个问题,我们正在延长学生的学业,但研究生不需要提交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`);
}
}
现在我们有了更好的方法来分离各自的职责。这个原则的名字可能有点吓人,但其原理其实很简单。
定义: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 */
}
}
两者都实现了 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;
}
}
非常简单!现在我们有两个接口了!雇主类和佣金接口。现在只有卖家类会实现这两个用于佣金的接口。接待员类不需要只实现员工类。所以接待员类不必被迫实现那些永远不会用到的方法。
定义: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);
}
}
我们注意到,存储库 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);
}
}
太棒了!好多了!现在我们在构造函数中接收存储库作为参数,以便实例化和使用。现在我们依赖于抽象,并且不需要知道我们正在使用哪个存储库。
定义: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/