嗨!你好吗?你还好吗?希望你还好!
今天我要谈论一个每个人都在谈论或写到的主题。但有时很难理解每一个原则。我说的是 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
最后一个!听名字你可能觉得很难记住!但你可能每次都见过这个原则。
假设您有一个与 Repository 类集成的服务类,该类将调用数据库,例如 Postgress。但是,如果存储库类发生变化,数据库也会更改为 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
精加工
那么你现在感觉如何?我希望通过这些简单的例子,你可以记住并理解在代码中使用这些原则的原因和内容。它使理解和扩展变得更容易,此外你正在应用干净的代码。