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()​ 等等等,还有很多就不一一列举了,这里先知道什么叫做面向切面编程就差不多辣~