搜尋
首頁web前端js教程掌握依賴倒置原則:使用 DI 實現乾淨程式碼的最佳實踐

如果您熟悉物件導向程式設計,或剛開始探索它,您可能遇到過縮寫SOLID。 SOLID 代表了一組旨在幫助開發人員編寫乾淨、可維護且可擴展的程式碼的原則。在這篇文章中,我們將重點放在 SOLID 中的“D”,它代表依賴倒置原則

但在深入了解細節之前,讓我們先花點時間了解這些原則背後的「原因」。

在物件導向程式設計中,我們通常將應用程式分解為類別,每個類別封裝特定的業務邏輯並與其他類別互動。例如,想像一個簡單的線上商店,用戶可以將產品添加到購物車中。此場景可以透過多個類別一起進行建模來管理商店的營運。讓我們以這個例子為基礎來探索依賴倒置原則如何改進我們系統的設計。

class ProductService {
 getProducts() {
   return ['product 1', 'product 2', 'product 3'];
 }
}


class OrderService {
 constructor() {
   this.productService = new ProductService();
 }

 getOrdersForUser() {
   return this.productService.getProducts();
 }
}


class UserService {
 constructor() {
   this.orderService = new OrderService();
 }

 getUserOrders() {
   return this.orderService.getOrdersForUser();
 }
}

如我們所見,像 OrderServiceProductService 這樣的依賴關係在類別建構子中緊密耦合。這種直接依賴使得替換或模擬這些元件變得困難,這在測試或交換實作時提出了挑戰。

依賴注入(DI)

依賴注入 (DI) 模式提供了這個問題的解決方案。透過遵循 DI 模式,我們可以解耦這些依賴關係,並使我們的程式碼更加靈活和可測試。以下是我們如何重構程式碼來實作 DI:

Mastering the Dependency Inversion Principle: Best Practices for Clean Code with DI

class ProductService {
 getProducts() {
   return ['product 1', 'product 2', 'product 3'];
 }
}


class OrderService {
 constructor(private productService: ProductService) {}

 getOrdersForUser() {
   return this.productService.getProducts();
 }
}


class UserService {
 constructor(private orderService: OrderService) {}

 getUserOrders() {
   return this.orderService.getOrdersForUser();
 }
}


new UserService(new OrderService(new ProductService()));

我們明確地將依賴項傳遞給每個服務的建構函數,這雖然是朝著正確方向邁出的一步,但仍然會導致緊密耦合的類別。這種方法確實稍微提高了靈活性,但它並沒有完全解決使我們的程式碼更加模組化且易於測試的根本問題。

依賴倒置原理(DiP)

依賴倒置原理(DiP)透過回答關鍵問題更進一步:我們應該傳遞什麼?這個原則表明,我們不應傳遞具體的實現,而應僅傳遞必要的抽象,特別是與預期介面匹配的依賴項。

例如,考慮帶有 getProducts 方法的 ProductService 類,該方法傳回 產品數組 。我們可以透過多種方式實現它,而不是直接將 ProductService 耦合到特定的實作(例如,從資料庫中取得資料)。一種實作可能從資料庫取得產品,而另一種實作可能傳回硬編碼的 JSON 物件以進行測試。關鍵是兩種實現共享相同的接口,確保靈活性和可互換性。

Mastering the Dependency Inversion Principle: Best Practices for Clean Code with DI

控制反轉 (IoC) 和服務定位器

為了將此原則付諸實踐,我們經常依賴一種稱為控制反轉 (IoC) 的模式。 IoC 是一種技術,將對依賴項的建立和管理的控制從類別本身轉移到外部元件。這通常是透過依賴注入容器或服務定位器來實現的,它充當註冊表,我們可以從中請求所需的依賴項。透過 IoC,我們可以動態地註入適當的依賴項,而無需將它們硬編碼到類別構造函數中,從而使系統更加模組化並且更易於維護。

Mastering the Dependency Inversion Principle: Best Practices for Clean Code with DI

class ProductService {
 getProducts() {
   return ['product 1', 'product 2', 'product 3'];
 }
}


class OrderService {
 constructor() {
   this.productService = new ProductService();
 }

 getOrdersForUser() {
   return this.productService.getProducts();
 }
}


class UserService {
 constructor() {
   this.orderService = new OrderService();
 }

 getUserOrders() {
   return this.orderService.getOrdersForUser();
 }
}

正如我們所看到的,依賴項是在容器內註冊的,這使得它們可以在必要時被替換或交換。這種靈活性是一個關鍵優勢,因為它促進了組件之間的鬆散耦合。

但是,這種方法有一些缺點。由於依賴項是在運行時解析的,因此如果出現問題(例如,如果依賴項遺失或不相容),可能會導致運行時錯誤。此外,無法保證註冊的依賴項將嚴格符合預期的接口,這可能會導致微妙的問題。這種依賴關係解析方法通常稱為服務定位器模式,並且在許多情況下被認為是反模式,因為它依賴執行時解析並且有可能掩蓋依賴關係。

InversifyJS

JavaScript 中用於實現 控制反轉 (IoC) 模式的最流行的庫之一是 InversifyJS。它提供了一個強大且靈活的框架,以乾淨、模組化的方式管理依賴關係。然而,InversifyJS 有一些缺點。一個主要限制是設定和管理依賴項所需的樣板程式碼量。此外,它通常需要以特定的方式建立應用程序,這可能不適合每個專案。

Mastering the Dependency Inversion Principle: Best Practices for Clean Code with DI

InversifyJS 的替代方案是Friendly-DI,這是一種輕量級且更簡化的方法,用於管理 JavaScript 和 TypeScript 應用程式中的依賴關係。它的靈感來自於 Angular 和 NestJS 等框架中的 DI 系統,但設計得更簡約、簡潔。

Friendly-DI 的一些主要優點包括:

  • 體積小:只有 2 KB,沒有外部依賴。
  • 跨平台:在瀏覽器和 Node.js 環境中無縫運作。
  • 簡單的 API:直覺且易於使用,只需最少的配置。
  • MIT 許可證:具有寬鬆許可的開源。

但是,需要注意的是,Friendly-DI 是專為 TypeScript 設計的,您需要先安裝其依賴項才能開始使用它。

class ProductService {
 getProducts() {
   return ['product 1', 'product 2', 'product 3'];
 }
}


class OrderService {
 constructor() {
   this.productService = new ProductService();
 }

 getOrdersForUser() {
   return this.productService.getProducts();
 }
}


class UserService {
 constructor() {
   this.orderService = new OrderService();
 }

 getUserOrders() {
   return this.orderService.getOrdersForUser();
 }
}

也擴充tsconfig.json:

class ProductService {
 getProducts() {
   return ['product 1', 'product 2', 'product 3'];
 }
}


class OrderService {
 constructor(private productService: ProductService) {}

 getOrdersForUser() {
   return this.productService.getProducts();
 }
}


class UserService {
 constructor(private orderService: OrderService) {}

 getUserOrders() {
   return this.orderService.getOrdersForUser();
 }
}


new UserService(new OrderService(new ProductService()));

上面的範例可以用Friendly-DI修改:

class ServiceLocator {
 static #modules = new Map();

 static get(moduleName: string) {
   return ServiceLocator.#modules.get(moduleName);
 }

 static set(moduleName: string, exp: never) {
   ServiceLocator.#modules.set(moduleName, exp);
 }
}

class ProductService {
 getProducts() {
   return ['product 1', 'product 2', 'product 3'];
 }
}


class OrderService {
 constructor() {
   const ProductService = ServiceLocator.get('ProductService');
   this.productService = new ProductService();
 }

 getOrdersForUser() {
   return this.productService.getProducts();
 }
}


class UserService {
 constructor() {
   const OrderService = ServiceLocator.get('OrderService');
   this.orderService = new OrderService();
 }

 getUserOrders() {
   return this.orderService.getOrdersForUser();
 }
}

ServiceLocator.set('ProductService', ProductService);
ServiceLocator.set('OrderService', OrderService);


new UserService();
  1. 正如我們所看到的,我們添加了 @Injectable() 裝飾器,它將我們的類別標記為可注入的,表明它們是依賴注入系統的一部分。這個裝飾器允許 DI 容器知道這些類別可以在需要的地方實例化和注入。

  2. 當在建構函式中將類別宣告為依賴項時,我們不會直接綁定到具體類別本身。相反,我們根據其介面來定義依賴關係。這將我們的程式碼與特定實作解耦,並提供更大的靈活性,從而在需要時更容易交換或模擬依賴項。

  3. 在此範例中,我們將 UserService 放置在 App 類別中。這種模式稱為組合根組合根是應用程式中組裝和注入所有依賴項的中心位置 - 本質上是我們應用程式依賴關係圖的「根」。透過將此邏輯保留在一個位置,我們可以更好地控制如何在整個應用程式中解析和注入依賴項。

最後一步是在 DI 容器中註冊 App 類,這將使容器能夠在應用程式啟動時管理生命週期和所有依賴項的注入。

Mastering the Dependency Inversion Principle: Best Practices for Clean Code with DI

npm i friendly-di reflect-metadata

如果我們需要替換應用程式中的任何類,我們只需要按照原始介面建立模擬類:

class ProductService {
 getProducts() {
   return ['product 1', 'product 2', 'product 3'];
 }
}


class OrderService {
 constructor() {
   this.productService = new ProductService();
 }

 getOrdersForUser() {
   return this.productService.getProducts();
 }
}


class UserService {
 constructor() {
   this.orderService = new OrderService();
 }

 getUserOrders() {
   return this.orderService.getOrdersForUser();
 }
}

然後使用替換方法,我們將可替換類別宣告為類比類別:

class ProductService {
 getProducts() {
   return ['product 1', 'product 2', 'product 3'];
 }
}


class OrderService {
 constructor(private productService: ProductService) {}

 getOrdersForUser() {
   return this.productService.getProducts();
 }
}


class UserService {
 constructor(private orderService: OrderService) {}

 getUserOrders() {
   return this.orderService.getOrdersForUser();
 }
}


new UserService(new OrderService(new ProductService()));

友善-DI我們可以多次替換:

class ServiceLocator {
 static #modules = new Map();

 static get(moduleName: string) {
   return ServiceLocator.#modules.get(moduleName);
 }

 static set(moduleName: string, exp: never) {
   ServiceLocator.#modules.set(moduleName, exp);
 }
}

class ProductService {
 getProducts() {
   return ['product 1', 'product 2', 'product 3'];
 }
}


class OrderService {
 constructor() {
   const ProductService = ServiceLocator.get('ProductService');
   this.productService = new ProductService();
 }

 getOrdersForUser() {
   return this.productService.getProducts();
 }
}


class UserService {
 constructor() {
   const OrderService = ServiceLocator.get('OrderService');
   this.orderService = new OrderService();
 }

 getUserOrders() {
   return this.orderService.getOrdersForUser();
 }
}

ServiceLocator.set('ProductService', ProductService);
ServiceLocator.set('OrderService', OrderService);


new UserService();

就這樣,如果您對此主題有任何意見或澄清,請在評論中寫下您的想法。

以上是掌握依賴倒置原則:使用 DI 實現乾淨程式碼的最佳實踐的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
JavaScript在行動中:現實世界中的示例和項目JavaScript在行動中:現實世界中的示例和項目Apr 19, 2025 am 12:13 AM

JavaScript在現實世界中的應用包括前端和後端開發。 1)通過構建TODO列表應用展示前端應用,涉及DOM操作和事件處理。 2)通過Node.js和Express構建RESTfulAPI展示後端應用。

JavaScript和Web:核心功能和用例JavaScript和Web:核心功能和用例Apr 18, 2025 am 12:19 AM

JavaScript在Web開發中的主要用途包括客戶端交互、表單驗證和異步通信。 1)通過DOM操作實現動態內容更新和用戶交互;2)在用戶提交數據前進行客戶端驗證,提高用戶體驗;3)通過AJAX技術實現與服務器的無刷新通信。

了解JavaScript引擎:實施詳細信息了解JavaScript引擎:實施詳細信息Apr 17, 2025 am 12:05 AM

理解JavaScript引擎內部工作原理對開發者重要,因為它能幫助編寫更高效的代碼並理解性能瓶頸和優化策略。 1)引擎的工作流程包括解析、編譯和執行三個階段;2)執行過程中,引擎會進行動態優化,如內聯緩存和隱藏類;3)最佳實踐包括避免全局變量、優化循環、使用const和let,以及避免過度使用閉包。

Python vs. JavaScript:學習曲線和易用性Python vs. JavaScript:學習曲線和易用性Apr 16, 2025 am 12:12 AM

Python更適合初學者,學習曲線平緩,語法簡潔;JavaScript適合前端開發,學習曲線較陡,語法靈活。 1.Python語法直觀,適用於數據科學和後端開發。 2.JavaScript靈活,廣泛用於前端和服務器端編程。

Python vs. JavaScript:社區,圖書館和資源Python vs. JavaScript:社區,圖書館和資源Apr 15, 2025 am 12:16 AM

Python和JavaScript在社區、庫和資源方面的對比各有優劣。 1)Python社區友好,適合初學者,但前端開發資源不如JavaScript豐富。 2)Python在數據科學和機器學習庫方面強大,JavaScript則在前端開發庫和框架上更勝一籌。 3)兩者的學習資源都豐富,但Python適合從官方文檔開始,JavaScript則以MDNWebDocs為佳。選擇應基於項目需求和個人興趣。

從C/C到JavaScript:所有工作方式從C/C到JavaScript:所有工作方式Apr 14, 2025 am 12:05 AM

從C/C 轉向JavaScript需要適應動態類型、垃圾回收和異步編程等特點。 1)C/C 是靜態類型語言,需手動管理內存,而JavaScript是動態類型,垃圾回收自動處理。 2)C/C 需編譯成機器碼,JavaScript則為解釋型語言。 3)JavaScript引入閉包、原型鍊和Promise等概念,增強了靈活性和異步編程能力。

JavaScript引擎:比較實施JavaScript引擎:比較實施Apr 13, 2025 am 12:05 AM

不同JavaScript引擎在解析和執行JavaScript代碼時,效果會有所不同,因為每個引擎的實現原理和優化策略各有差異。 1.詞法分析:將源碼轉換為詞法單元。 2.語法分析:生成抽象語法樹。 3.優化和編譯:通過JIT編譯器生成機器碼。 4.執行:運行機器碼。 V8引擎通過即時編譯和隱藏類優化,SpiderMonkey使用類型推斷系統,導致在相同代碼上的性能表現不同。

超越瀏覽器:現實世界中的JavaScript超越瀏覽器:現實世界中的JavaScriptApr 12, 2025 am 12:06 AM

JavaScript在現實世界中的應用包括服務器端編程、移動應用開發和物聯網控制:1.通過Node.js實現服務器端編程,適用於高並發請求處理。 2.通過ReactNative進行移動應用開發,支持跨平台部署。 3.通過Johnny-Five庫用於物聯網設備控制,適用於硬件交互。

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱工具

SublimeText3 英文版

SublimeText3 英文版

推薦:為Win版本,支援程式碼提示!

Dreamweaver Mac版

Dreamweaver Mac版

視覺化網頁開發工具

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

EditPlus 中文破解版

EditPlus 中文破解版

體積小,語法高亮,不支援程式碼提示功能