- 原文地址:Dependency Injection in TypeScript
- 原文做者:Mert Türkmenoğlu
- 譯文出自:掘金翻譯計劃
- 本文永久連接:github.com/xitu/gold-m…
- 譯者:Usualminds
- 校對者:Kim Yang、PassionPenguin
每個軟件程序都有其最基礎的構建模塊。在面向對象的編程語言中, 咱們使用類去構建複雜的體系架構。像建一幢大樓,咱們把模塊之間創建的聯繫稱之爲依賴。其餘的類爲了支持咱們類的需求,提供複雜的封裝操做。前端
一個類可能有引用其餘類的字段。所以,咱們不得不問及這些問題:這些引用是怎麼被建立的?是咱們去組合這些對象,仍是其餘類負責實例化它們?若是要實例化的類太複雜,而且咱們想避免出現垃圾代碼?全部這些問題均可以試圖經過依賴注入原則來解決。node
在開始示例以前,咱們必需要去理解關於依賴注入的一些相關概念。依賴注入原則告訴咱們,一個類應該去接收而非實例化它的依賴。經過委託方式來進行對象初始化,這能夠處理較爲複雜的操做,從而減小在類設計上的壓力。你能夠移除代碼中複雜的模塊,並經過其餘方式從新引入依賴。如何處理移除和從新引入依賴,這是依賴管理的問題。你能夠手動處理全部對象的初始化和注入,可是這將會使整個系統變得複雜,咱們要儘可能避免這種狀況的發生。相反,你能夠將構造的職責轉移到 IoC 容器。android
控制反轉是經過反轉整個程序的流程,以便容器對全部程序中涉及的依賴進行管理。你能夠建立一個容器,整個容器負責構造對象。當一個類須要實例化對象時,IoC 容器能夠提供它所須要的依賴。ios
IoC 只提供了一種方法而非具體的實現。爲了使用依賴注入原則,你須要一個依賴注入框架。舉例以下:git
在依賴注入原則中,咱們須要理解四種不一樣的角色:github
服務端是咱們對外暴露服務使用的。這些類由 IoC 容器實例化和使用。一個客戶端經過 IoC 容器來使用這些服務。客戶端不該該被具體細節所困擾,所以接口須要確保客戶端和服務端保持協調。客戶端請求所需的依賴,注入器提供實例化服務。typescript
當咱們討論如何在類中注入依賴時,能夠經過三種不一樣方式來實現:數據庫
一旦咱們理解了依賴注入的基本原理,使用什麼框架或者庫差異並不大。這篇文章裏,我選擇了 TypeScript 語言和 TypeDI 庫來展現這些基本概念。express
初始化 Yarn 和添加 TypeScript 會花點時間。我不想使用有名氣但沒有足夠註釋的項目配置,由於這會讓你感到無趣。因此我將給出初步的代碼並作簡要介紹。你能夠從這個 Github 倉庫查看和下載代碼。編程
任何 TypeScript 項目均可以做爲依賴注入演示的例子。但這篇文章裏我選擇了一個 Node/Express 應用做爲示例。我假設使用 TypeScript 的開發者要麼直接使用 Node/Express 服務器,要麼對它們有所瞭解。
當你查看 package.json
文件時,你能夠看到這些依賴項配置,讓我簡要介紹下它們:
你須要留意一些重要的編譯器選項配置。若是你看下 tsconfig.json,你能夠看到編譯過程的配置選項:
全部的源碼都在 src 文件夾下。src/index.ts 是咱們項目的入口文件。這個文件包含了服務器的全部引導步驟:
import 'reflect-metadata';
import express from 'express';
import Container from 'typedi';
import UserController from './controllers/UserController';
const main = async () => {
const app = express();
const userController = Container.get(UserController);
app.get('/users', (req, res) => userController.getAllUsers(req, res));
app.listen(3000, () => {
console.log('Server started');
});
}
main().catch(err => {
console.error(err);
});
複製代碼
這段代碼是一個只有一個端口的小型 Express 服務器。當你向 /users 路由發送一個 GET 請求時,它會返回一個用戶列表。main 函數的核心是 Container.get 方法。注意咱們並無使用 new 關鍵字或者實例化對象。咱們只是調用 IoC 容器返回的一個 UserController 實例方法。而後綁定了路由和控制器方法。
咱們的應用程序是一個虛擬的 RESTful 服務器,但我不想讓它沒有一點意義。我添加了四個不一樣的文件夾表明一個完備後端服務的基本部分。它們是 controllers、models、repositories 和 services。如今讓我一個個介紹下它們:
咱們不會把全部的東西都放到一個類中。請求和響應之間還有不少層級。這就是所謂的分層架構。經過類之間的依賴共享,咱們能夠更容易地進行依賴注入。
import { Request, Response } from "express";
import { Service } from "typedi";
import UserService from "../services/UserService";
@Service()
class UserController {
constructor(private readonly userService: UserService) { }
async getAllUsers(_req: Request, res: Response) {
const result = await this.userService.getAllUsers();
return res.json(result);
}
}
export default UserController;
複製代碼
UserController 只有一個方法。getAllUsers
方法負責從用戶服務中獲取結果並進行傳輸。咱們給 UserController 添加類一個 Service 裝飾器,由於咱們但願這個類由 IoC 容器進行管理。在構造函數方法內部,咱們能夠看到這個類須要一個 UserService 實例。一樣,咱們不須要控制這個依賴關係。由於 TypeDI 容器爲 UserService 建立了一個實例,當它生成 UserController 實例時,它將注入到 UserService 中。
import { Service } from "typedi";
import User from "../models/User";
import UserRepository from "../repositories/UserRepository";
@Service()
class UserService {
constructor(private readonly userRepository: UserRepository) { }
async getAllUsers(): Promise<User[]> {
const result = await this.userRepository.getAllUsers();
return result;
}
}
export default UserService;
複製代碼
UserService 和 UserController 很相似。咱們向類添加一個 Service 裝飾器,並在構造函數方法中指定它們想要的依賴項。
import { Service } from "typedi";
import User from "../models/User";
@Service()
class UserRepository {
private readonly users: User[] = [
{ name: 'Emily' },
{ name: 'John' },
{ name: 'Jane' },
];
async getAllUsers(): Promise<User[]> {
return this.users;
}
}
export default UserRepository;
複製代碼
UserRepository 是咱們的最後一步。咱們用 Service 來註解這個類,可是咱們沒有任何依賴關係。由於沒有數據庫鏈接,因此我只是將已硬編碼過的用戶列表做爲私有屬性添加到類中。
依賴注入是管理複雜對象初始化的有力工具。手動進行依賴注入總比什麼都不作要好,可是使用 TypeDI 更簡單可行。當你要開始作一個新項目時,你應該明確地考慮下依賴注入原則並給予適當嘗試。
你能夠在這個 GitHub 分支找到本文的代碼。
你能夠在 GitHub、LinkedIn 和 Twitter 找到我。
感謝閱讀,祝你快樂。
若是發現譯文存在錯誤或其餘須要改進的地方,歡迎到 掘金翻譯計劃 對譯文進行修改並 PR,也可得到相應獎勵積分。文章開頭的 本文永久連接 即爲本文在 GitHub 上的 MarkDown 連接。
掘金翻譯計劃 是一個翻譯優質互聯網技術文章的社區,文章來源爲 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智能等領域,想要查看更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。