用 TypeScript 簡單介紹 SOLID 原則裡面的 D
此為 SOLID 原則介紹的系列文章之一,所有文章的連結如下。
- 用 TypeScript 解釋 SOLID 原則裡的 S
- 用 PHP 解釋 SOLID 原則裡的 O
- 用 TypeScript 簡單介紹 SOLID 原則裡面的 L
- 用 PHP 簡單介紹 SOLID 原則裡面的 I
- 用 TypeScript 簡單介紹 SOLID 原則裡面的 D
SOLID 原則中的 D
來到 SOLID 原則中最後一個原則,也就是依賴倒轉原則 (Dependency inversion principle)。
DIS 最主要的目的在於解耦 (Decoupling),其概念如下。
高層次的類別不應該依賴於低層次的類別,兩者都應該依賴於抽象的介面。
抽象介面不應該依賴於具體的實作,具體的實作應該依賴於抽象介面。
什麼是高層次與低層次的類別呢?
- 低層次的類別,用來實作基礎操作,例如磁碟操作,網路數據傳輸和資料庫連線。
- 高層次的類別,包含複雜的業務邏輯,操作低層次的類別來執行特定操作。
有時候在開發新系統的時候,我們可能會先設計低層次的類別,然後才會開始開發高層次的類別。
這樣開發流程十分常見,因為在低層次類別的功能還沒有確定與實作之前,我們也無法確定高層次類別可以實現哪些功能。
但這樣的開發流程,容易讓用來實現業務邏輯的高層次類別依賴於低層次的類別。
依賴倒轉原則建議改變這種依賴方式。
- 最好使用業務術語來命名高層次類別的方法,如
BudgetReport
。 - 基於抽象介面建立高層次類別,而不是基於低層次的具體類別,這會比原始的依賴關係靈活很多。
- 一旦低層次的類別實現這些介面,他們將依賴於業務邏輯層,從而倒轉了原始的依賴關係。
依賴倒轉原則通常會伴隨著開放封閉原則,無須修改已存在的類別,就能擴展不同的業務邏輯。
舉個例子,高層次的預算報告類別 BudgetReport
,使用低層次的資料庫類別 MySqlDatabase
來讀取和儲存資料,這代表低層次類別的任何改變 (例如資料庫發布新版本時),都會影響高層次的類別,但高層次的類別不應該去關注資料存儲的細節。
class MySqlDatabase {
insert() {
// ...
}
update() {
// ...
}
delete() {
// ...
}
}
class BudgetReport {
private mySqlDatabase: MySqlDatabase
constructor(mySqlDatabase: MySqlDatabase) {
this.mySqlDatabase = mySqlDatabase
}
open(date: string) {
//...
}
save() {
}
}
可以看到上述的程式碼,高層次的類別 BudgetReport
依賴於低層次的類別 MySqlDatabase
。
要解決這個問題,我們可以建立一個描述讀寫操作的高層次介面 Database
,並讓預算報告類別 BudgetReport
使用這個介面,而不是使用低層次的類別 MySqlDatabase
。
interface Database {
insert(): void
update(): void
delete(): void
}
class MySql implements Database {
insert(): void {
// ...
}
update(): void {
// ...
}
delete(): void {
// ...
}
}
class MongoDB implements Database {
insert(): void {
// ...
}
update(): void {
// ...
}
delete(): void {
// ...
}
}
class BudgetReport {
private database: Database
constructor(database: Database) {
this.database = database
}
open(date: string) {
//...
}
save() {
}
}
修改之後,低層次的類別依賴於高層次的抽象,原始的依賴關係被倒轉。
Laracasts 的講師是這麼說明依賴倒轉原則的。
Depends on abstractions, not on concretions.
依賴於抽象,而非具體。