阿川私房教材:學程式,拿 offer!

63 個專案實戰,直接上手!
無需補習,按步驟打造你的面試作品。

立即解鎖你的轉職秘笈

你好呀!你最近怎麼樣?你沒事吧?但願如此!

今天我要談一個每個人都在談論或寫到的主題。但有時要理解每一個原則是很困難的。我說的是固體。

當我問及 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。他只是研究和尋找。

我們要怎麼解決這個問題呢?簡單的!讓我們建立一個學生類,並將畢業學生和畢業後學生分開:

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。但是,例如,如果 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

結論

那你現在感覺怎麼樣?我希望透過這個簡單的範例,您可以記住並理解在程式碼中使用這些原則的內容和原因。除了應用乾淨的程式碼之外,它還更容易理解和擴展。

我希望你喜歡!

非常感謝您,祝您永遠健康!

聯絡方式:

領英:https://www.linkedin.com/in/kevin-uehara/

Instagram:https://www.instagram.com/uehara\_kevin/

推特: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

按讚的人:

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。

阿川私房教材:學程式,拿 offer!

63 個專案實戰,直接上手!
無需補習,按步驟打造你的面試作品。

立即解鎖你的轉職秘笈