SOLID 是你最需要的程式設計原則!

SOLID 是你最需要的程式設計原則!

剛開始物件導向程式設計,不知道SOLID?別擔心,在本文中我將向您解釋它並舉例說明如何在開發程式碼時使用它。

[什麼是 SOLID?](#什麼是 SOLID)

S - 單一責任原則

開閉原則

L - 里氏替換原理

I - 介面隔離原則

D - 依賴倒置原則

結論

什麼是實體?

在物件導向程式設計中,術語 SOLID 是五個設計假設的縮寫,旨在促進理解、開發和維護軟體。

當使用這套原則時,可以顯著減少錯誤的產生,提高程式碼質量,產生更有組織的程式碼,減少耦合,改進重構並鼓勵程式碼重複使用。

S - 單一職責原則

SRP - 單一職責原則

這原則說一個類別必須有一個且只有一個改變的理由

就是這樣,不要建立具有多種功能和職責的類別。您可能已經完成或遇到一個可以做所有事情的類,例如“God Class”。在那一刻看起來一切都很好,但是當需要對這個類別的邏輯進行任何更改時,問題肯定會開始出現。

God Class - God Class: 在物件導向程式設計中,它是一個知道太多或做太多事情的類別。

class Task {

createTask(){/*...*/}

updateTask(){/*...*/}

deleteTask(){/*...*/}

showAllTasks(){/*...*/}

existsTask(){/*...*/}

TaskCompleter(){/*...*/}

}

這個 **Task** 類別透過執行 **四個** 不同的任務來打破 **SRP** 原則。它正在處理**任務**的資料、顯示、驗證和驗證。

### 這可能導致的問題:

- 「缺乏連結」-一個類別不應該承擔不屬於它自己的責任;

- 「太多的資訊在一起」 - 你的類別將有很多依賴項並且很難進行更改;

- 「實現自動化測試的困難」 - 很難[“mock”](https://pt.wikipedia.org/wiki/Objeto_Mock)這種類型的類別;

現在將 **SRP** 應用於 *Task* 類,讓我們看看這個原則可以帶來的改進:

class TaskHandler{

createTask() {/.../}

updateTask() {/.../}

deleteTask() {/.../}

}

class TaskViewer{

showAllTasks() {/.../}

}

class TaskChecker {

existsTask() {/.../}

}

class TaskCompleter {

completeTask() {/.../}

}

>您可以將建立、更新和刪除放在單獨的類別中,但根據專案的上下文和大小,最好避免不必要的複雜性。

也許您問過自己「我只能將其應用於類別嗎?」不,相反,您也可以將其應用於方法和函數。

//❌

function emailClients(clients: IClient[]) {

clients.forEach((client)=>{

const clientRecord = db.find(client);

if(clientRecord){

sendEmail(client);

}

})

}

//✅

function isClientActive(client: IClient):boolean {

const clientRecord = db.find(client);

return !!clientRecord;

}

function getActiveClients(clients: IClient[]): {

return clients.filter(isClientActive);

}

function emailClients(clients: IClient[]):void {

const activeClients = getActiveClients(clients);

activeClients?.forEach(sandEmail);

}

更美觀、優雅、更有組織的程式碼。這個原則是其他原則的基礎,透過應用它,您將建立優質、易於閱讀和易於維護的程式碼。

## O - 開閉原則

![開閉原則範例](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qk0e24cnjefd5e8r0h6c.png)

>*OCP - 開閉原則*

這個原則說的是**物件或實體必須對擴充功能開放,但對修改關閉**,如果需要加入功能,最好對其進行擴展而不是更改其原始程式碼。

想像一個學校辦公室的小型系統,其中有兩個班級代表學生的課程表:小學和高中。另外還有一個班級,定義了學生的班級。

class EnsinoFundamental {

gradeCurricularFundamental(){}

}

class EnsinoMedio {

gradeCurricularMedio(){}

}

class SecretariaEscola {

aulasDoAluno: string;

cadastrarAula(aulasAluno){

if(aulasAluno instanceof EnsinoFundamental){

this.aulasDoAluno = aulasAluno.gradeCurricularFundamental();

} else if(aulasAluno.ensino instanceof EnsinoMedio){

this.aulasDoAluno = aulasAluno.gradeCurricularMedio();

}

}

}

`SecretariaEscola` 類別負責檢查學生的教育程度,以便在註冊課程時應用正確的業務規則。現在想像一下,這所學校在系統中加入了技術教育和課程表,那麼就需要修改這個課程,對吧?但是,這樣你就會遇到一個問題,那就是違反了*SOLID 的「開閉原則” *。

我想到了什麼解決方案?可能在類別中加入一個“else if”,就這樣,問題解決了。不是小學徒😐,這就是問題所在!

**透過更改現有類別以加入新行為,我們面臨著將錯誤引入到已經執行的內容中的嚴重風險。**

>**記住:** **OCP** 認為課程必須針對更改關閉並針對擴充功能開放。

看看重構程式碼所帶來的美妙之處:

interface gradeCurricular {

gradeDeAulas();

}

class EnsinoFundamental implements gradeCurricular {

gradeDeAulas(){}

}

class EnsinoMedio implements gradeCurricular {

gradeDeAulas(){}

}

class EnsinoTecnico implements gradeCurricular {

gradeDeAulas(){}

}

class SecretariaEscola {

aulasDoAluno: string;

cadastrarAula(aulasAluno: gradeCurricular) {

this.aulasDoAluno = aulasAluno.gradeDeAulas();

}

}

看到 `SecretariaEscola` 類,它不再需要知道要呼叫哪些方法來註冊該類別。它將能夠為建立的任何新型教學模式正確註冊課程表,請注意,我加入了“EnsinoTecnico”,無需更改原始程式碼。

>*自從我實作了 `gradeCurrarily` 介面以來。*

>介面背後的獨立可擴展行為和反向依賴關係。

>鮑伯叔叔

- `開放擴充`:您可以為類別加入一些新功能或行為,而無需更改其原始程式碼。

-「修改關閉」:如果您的類別已經具有不存在任何問題的功能或行為,請勿變更其原始程式碼以新增內容。

## L - 里氏替換原則

![里氏替換原理範例](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eyrl3p96aqzowf72ipcb.png)

>*LSP - 里氏替換原理*

里氏替換原則 — **A** **衍生類別必須可以被其基底類別取代**。

*兄弟* Liskov 在 1987 年的一次會議上介紹的這個原理在閱讀他的解釋時有點難以理解,但是不用擔心,我將向您展示另一個解釋和一個示例來幫助您理解。

> 如果對於 S 類型的每個物件 o1 都有一個 T 類型的物件 o2,這樣,對於用 T 定義的所有程式 P,當 o1 被 o2 取代時 P 的行為不變,那麼 S 是 T 的子類型

你明白了嗎?不,我第一次讀它時(或其他十次)也不明白它,但等等,還有另一種解釋:

> 如果 S 是 T 的子類型,則程式中類型 T 的物件可以用類型 S 的物件替換,而不必變更該程式的屬性。 - [維基百科](https://pt.wikipedia.org/wiki/Princ%C3%ADpio_da_substitui%C3%A7%C3%A3o_de_Liskov)。

如果您更直觀,我有一個程式碼範例:

class Fulano {

falarNome() {

return "sou fulano!";

}

}

class Sicrano extends Fulano {

falarNome() {

return "sou sicrano!";

}

}

const a = new Fulano();

const b = new Sicrano();

function imprimirNome(msg: string) {

console.log(msg);

}

imprimirNome(a.falarNome()); // sou fulano!

imprimirNome(b.falarNome()); // sou sicrano!

父類別和衍生類別作為參數傳遞,並且程式碼繼續按預期工作,神奇嗎?沒什麼,這就是我們利斯科夫兄弟的原則。

### 違規範例:

- 覆蓋/實作一個不執行任何操作的方法;

- 拋出意外的異常;

- 從基底類別傳回不同類型的值;

## I - 介面隔離原則

![範例介面隔離原則](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p88do8ivd00s9aofo5yq.png)

>*ISP - 介面隔離原則*

介面隔離原則 — **不應強迫類別實作它不會使用的介面和方法。**

該原則表明,建立更具體的介面比建立通用介面更好。

在下面的範例中,建立了一個「Animal」接口來抽象化動物行為,然後類別實作該接口,請參閱:

interface Animal {

comer();

dormir();

voar();

}

class Pato implements Animal{

comer(){/faz algo/};

dormir(){/faz algo/};

voar(){/faz algo/};

}

class Peixe implements Animal{

comer(){/faz algo/};

dormir(){/faz algo/};

voar(){/*faz algo*/};

// Esta implementação não faz sentido para um peixe

// ela viola o Princípio da Segregação da Interface

}

通用介面「Animal」強制「Peixe」類別具有有意義的行為,最終違反了 **ISP** 原則和 **LSP** 原則。

使用 **ISP** 解決此問題:

interface Animal {

comer();

dormir();

}

interface AnimalQueVoa extends Animal {

voar();

}

class Peixe implements Animal{

comer(){/faz algo/};

dormir(){/faz algo/};

}

class Pato implements AnimalQueVoa {

comer(){/faz algo/};

dormir(){/faz algo/};

voar(){/faz algo/};

}

現在更好了,“voar()”方法已從“Animal”介面中刪除,我們將其加入到派生介面“AnimalQueVoa”中。這樣,行為就在我們的上下文中被正確隔離,並且我們仍然尊重介面隔離的原則。

## D - 依賴倒置原則

![依賴倒置原則範例](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9s5ywa9xijb41c1z2l1a.png)

> *DIP — 依賴倒置原理*

依賴倒置原則 — **依賴抽象,而不是實現。**

> 1. 高層模組不應該依賴低層模組。兩者都必須依賴抽象。

>

> 2. 抽像不應該依賴細節。細節必須依賴抽象。

>

> - *叔叔鮑伯*

在下面的範例中,我將展示一個簡單的程式碼來說明**DIP**。在此範例中,我們有一個透過不同方式(例如電子郵件和簡訊)發送訊息的通知系統。首先讓我們為這些通知方式建立具體的類別:

class EmailNotification {

send(message) {

console.log(Enviando e-mail: ${message});

}

}

class SMSNotification {

send(message) {

console.log(Enviando SMS: ${message});

}

}

現在,讓我們建立一個依賴這些具體實作的服務類別:

class NotificationService {

constructor() {

this.emailNotification = new EmailNotification();

this.smsNotification = new SMSNotification();

}

sendNotifications(message) {

this.emailNotification.send(message);

this.smsNotification.send(message);

}

}

在上面的例子中,`NotificationService`直接依賴`EmailNotification`和`SMSNotification`的具體實作。這違反了 DIP,因為高級 `NotificationService` 類別直接依賴低階類別。

讓我們使用 **DIP** 修復此程式碼。高級“NotificationService”類別不應依賴具體實現,而應依賴抽象。讓我們建立一個「Notification」介面作為抽象:

// Abstração para o envio de notificações

interface Notification {

send(message) {}

}

現在,具體的「EmailNotification」和「SMSNotification」實作必須實作此介面:

class EmailNotification implements Notification {

send(message) {

console.log(Enviando e-mail: ${message});

}

}

class SMSNotification implements Notification {

send(message) {

console.log(Enviando SMS: ${message});

}

}

最後,通知服務類別可以依賴「Notification」抽象:

class NotificationService {

constructor(notificationMethod: Notification) {

this.notificationMethod = notificationMethod;

}

sendNotification(message) {

this.notificationMethod.send(message);

}

}

這樣,「NotificationService」服務類別依賴「Notification」抽象,而不是具體實現,從而滿足**依賴倒置原則**。

## 結論

透過採用這些原則,開發人員可以建立更能適應變化的系統,使維護變得更容易,並隨著時間的推移提高程式碼品質。

所有這些內容都是基於我學習 OOP 期間在網上找到的筆記、其他文章和影片,其中的解釋接近原理的作者,而示例中使用的程式碼是我根據自己對 OOP 的理解建立的。原則。讀者,我希望我對您的學習進程有所幫助。

---

原文出處:https://dev.to/clintonrocha98/era-solid-o-que-me-faltava-bhp

相关推荐

《仙境传说RO手游》BOSS刷新时间汇总解析
英国正版365app下载

《仙境传说RO手游》BOSS刷新时间汇总解析

📅 09-16 👁️ 9970
淘云互动机器人_淘云互动app机器人
外勤365系统

淘云互动机器人_淘云互动app机器人

📅 07-07 👁️ 987
水命怎么取名(水命的人取名字有什么讲究)
外勤365系统

水命怎么取名(水命的人取名字有什么讲究)

📅 07-21 👁️ 6302