- 原文地址: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、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。