Nest.js系列01:一些预备知识
预备知识:控制反转、依赖注入、面向切面编程。
控制反转即:Inversion of Control
,简称 IoC
。
依赖注入即:Dependency Injection
, 简称 DI
。
面向切面编程即:Aspect-Oriented Programming
,简称 AOP
。
它们都是面向对象编程中的编程思想,目的是帮助我们尽可能地提高代码可维护性和可扩展性,下面来逐步详细介绍它们。
控制反转(IoC)
💡
控制反转(IoC)
是一种将控制权转移到外部来管理对象之间的依赖关系的一种方式。
在传统的面向对象编程中,一个对象通常需要直接创建和管理它所依赖的其他对象。这种方式会导致代码之间的紧耦合,使代码难以维护和扩展。而使用控制反转(IoC),我们可以将对象之间的依赖关系交由一个外部容器或框架来管理,从而使代码之间的关系更加松散,便于维护和扩展。总结:
控制反转(IoC)
是一种解耦方式,可以使代码更加灵活。
每个字都认识,就是看着不咋像人话
只看概念太抽象了,举个最小实现的例子 🌰 就很容易理解了:
// 在传统的面向对象编程中,UserController类通常直接实例化Service类,并调用其方法来完成业务逻辑
class UserService {
find() {
return ["Find some users"];
}
}
class UserController {
constructor() {
// 注意这里,UserController 显式地依赖于 UserService
// 导致两个类之间产生紧耦合的关系
this.userService = new UserService();
}
getUsers() {
return this.userService.find();
}
}
下面使用控制反转的思想来重写上面的例子:
class UserService {
find() {
return ["Find some users"];
}
}
class UserController {
// 注意这里,我们选择将 Service 的实例传递给 Controller 的构造函数
// 这样一来, Controller 类就不再需要直接实例化 Service 类,而是依赖于外部传入的 Service 实例
// 控制反转的思想:将对象的创建过程交给外部容器来管理,从而减少对象之间的耦合
// 这样,Controller 就可以使用任何实现了 Service 接口的类,而不需要修改自己的代码
constructor(userService) {
this.userService = userService;
}
getUsers() {
return this.userService.find();
}
}
额,传个参数就变成控制反转了?
哈,反差!虽然概念看上去好像很难让人理解,但这个极其简单的例子体现的的确就是控制反转的核心思想 —— 控制权转移到外部,减少对象之间的耦合。看着例子是不是感觉好理解多了?
接下来,再来看看依赖注入的核心思想,在实践过程中,一般控制反转(IoC)、依赖注入(DI)都是结合起来使用的。
依赖注入(DI)
💡 依赖注入顾名思义,主要是将关联的类之间的依赖关系以参数的形式传递(即注入)。它可以看作是控制反转的一种具体实现方式,它将依赖关系从代码中移出来,一般通过容器来管理依赖关系,使代码更加简洁、可读和易于维护。
以上面的例子为基础继续扩充:
class UserService {
constructor() {
this.users = [];
}
// 模拟创建用户的业务逻辑
create(user) {
this.users.push(user);
}
// 模拟获取用户的业务逻辑
find() {
return this.users;
}
}
class UserController {
constructor(userService) {
this.userService = userService;
}
// 模拟创建用户的API
create(user) {
this.userService.create(user);
}
// 模拟获取用户的API
find() {
return this.userService.find();
}
}
// 使用一个Map对象模拟依赖注入容器,通过容器去统一的管理依赖关系
const container = new Map();
container.set("UserService", new UserService());
container.set("UserController", new UserController(container.get("UserService")));
const userController = container.get("UserController");
userController.create("张三");
userController.create("李四");
console.log(userController.find());
对之前的例子做了一点点修改,并且在最后使用 Map 对象模拟容器。
在实际的使用中,因为业务会越来越复杂,所以一般都会像例子中使用一个依赖注入容器去统一管理依赖,让代码更加简洁,易于维护。当然了,实际实现的依赖注入容器肯定不会那么简单,例子中只求神似,核心思想是这样的没错(点头
另外,在平常使用 Nest.js 的过程中,其实并不会去操心这些,框架已经封装好了。
面向切面编程(AOP)
💡 面向切面编程是一种以横向方式增强代码的技术。所谓的横向方式,指的是在不改变原有代码的前提下,通过在特定的执行位置插入代码来增强程序功能。这些代码通常被称为“切面”,并且它们与应用的主要业务逻辑是分离的。这种方式可以提高代码的重用性、可维护性和可扩展性。
看到上面有没有感觉有点熟悉呢?不改变原有代码,在特定的位置插入代码来增强功能,这不就是 TypeScript 中的装饰器嘛!
没错!在 Nest.js 中,正是使用了类装饰器、方法装饰器、属性装饰器这些 TypeScript 的语言特性来实现的 AOP。
举个例子:
// app.module.ts
// 使用类装饰器声明一个模块
@Module()
export class AppModule {}
// app.controller.ts
// 使用类装饰器声明一个控制器
@Controller()
export class AppController {
// 使用方法装饰器声明一个支持GET方法的路由
@Get()
async getHello() {
return 'Hello, World.';
}
}
平常开发中常用的还有:@Post()
, @Param()
, @Query()
, @Headers()
, @Body()
, @Req()
, @Res()
等等等,还有很多就不一一列举了,这里先知道什么叫做面向切面编程就差不多辣~