工厂模式与 Nest.js 核心思想 ------ 从蜜雪冰城到企业级架构
本篇通过蜜雪冰城的真实故事,带你理解工厂模式的设计哲学,并以此切入 Nest.js 的模块、控制器、服务、依赖注入与装饰器,彻底搞懂一个 Nest 应用是如何"活"起来的。
一、为什么需要设计模式?从"点奶茶"说起
笔记里有一句话:
"你想喝奶茶,不用自己做(流程代码NO)找蜜雪冰城。"
这其实就是面向接口编程 的朴素体现。作为消费者,我们不关心奶茶的制作流程(放多少茶、加多少糖),只需要知道"蜜雪冰城"这个工厂能提供我们想要的产品。
在代码世界,如果我们直接 new IceCream()、new LemonTea(),每新增一种产品,所有使用的地方都要修改。这就是紧耦合。
工厂模式 应运而生:把对象的创建逻辑封装到一个工厂类中,使用者只需要告诉工厂"我要什么类型",工厂返回对应的实例。这正是笔记中 MixueFactory 的代码所演示的。
javascript
class MixueFactory {
static create(type) {
switch(type) {
case 'ice': return new IceCream();
case 'lemon': return new LemonTea();
case 'milk': return new MilkTea();
}
}
}
这样带来的好处:
- 使用者(开发者)只需记住
create('ice')即可,不需要知道IceCream类的内部细节。 - 如果未来产品构造方式变了(比如增加参数),只需要修改工厂类,所有调用处无需改动。
- 所有产品类都实现了相同的
show()方法,保证了行为的一致性。
笔记中的关键句:
"开发者只需要调用
MixueFactory.create('type')即可。由于工厂里的每个类都实现了相同的 show 接口,由工厂类生产出来的类,可以放心的直接调用。"
二、Nest.js 中的工厂模式:NestFactory
在 Nest.js 中,main.ts 里我们看到了这样的代码:
typescript
const app = await NestFactory.create(AppModule);
NestFactory 就是一个工厂类 ,它负责创建整个 Nest 应用实例。我们不需要关心内部如何初始化中间件、如何配置底层 HTTP 服务器(Express/Fastify),只需要传入根模块 AppModule,就能得到一个可运行的应用对象。
这完美对应了蜜雪冰城的故事:
- 蜜雪冰城工厂 →
NestFactory - 点一杯柠檬水 →
NestFactory.create(AppModule) - 工厂返回产品实例 → 返回
app对象,我们可以调用app.listen(3000)
三、Nest 的模块化 ------ @Module 装饰器解读
笔记里写道:
"Module 是一个整体,后端最常见的 MVC 模式。一个文件几千行代码,组织控制器 controller, service 层 CRUD。"
Nest 用 @Module() 装饰器把控制器、服务组织成一个逻辑单元。来看 app.module.ts 的代码:
typescript
@Module({
imports: [], // 依赖外界?
controllers: [AppController], // 控制器 校验,简单逻辑,
providers: [AppService], // data service 复杂业务
})
export class AppModule {}
对注释的深度解读:
-
imports: [] // 依赖外界?这个数组用于导入其他模块。比如我们后续会导入
TodosModule,这意味着本模块需要用到其他模块提供的服务。它就像是"我要从隔壁部门借调人手",只有导入的模块在exports中暴露了服务,当前模块才能使用。 -
controllers: [AppController] // 控制器 校验,简单逻辑控制器只负责"接客" ------ 接收请求、提取参数、调用服务、返回响应。**"校验"严格来说是由
ValidationPipe管道完成的,控制器本身不写校验逻辑,但它是校验的"入口"。"简单逻辑"**意味着控制器里不能有复杂的业务计算,只能做路由调度。 -
providers: [AppService] // data service 复杂业务这里注册的是所有
@Injectable()的类,它们负责真正的业务逻辑、数据库操作、复杂计算。这就是 Service 层 ,是 MVC 中的 Model(模型) 的体现,但 Nest 更倾向于称为 Provider。
四、控制器与服务 ------ 依赖注入的魔法
在 app.controller.ts 中:
typescript
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
// ...
}
这行代码的底层逻辑:
- TypeScript 编译时,
constructor参数的类型信息被保留在元数据中(design:paramtypes)。 - Nest 在启动时扫描所有
@Controller()类,发现AppController需要AppService。 - 它去 IoC 容器中查找是否已有
AppService的实例(因为AppService被@Injectable()标记,已在providers中注册)。 - 如果有,直接注入;如果没有,先创建再注入。
- 注入后,
this.appService就可以直接使用了。
这就是依赖注入(DI)的本质 :控制反转 ------ 不是我们主动去 new,而是容器把依赖"送"给我们。这大大降低了耦合,也方便了单元测试(可以注入 Mock 对象)。
五、装饰器模式 ------ @ 符号到底是什么?
笔记里说:
"装饰器模式在不修改原有对象的前提下,动态给对象添加叠加额外功能。"
但 Nest 中的 @Controller、@Module、@Injectable 并不是经典装饰器模式(运行时套壳),它们是 TypeScript 装饰器 ,本质上是一个高阶函数 ,在类定义时执行,用于给类添加元数据(metadata)。
例如 @Module({...}) 会在 AppModule 类上挂载配置信息,Nest 启动时会读取这些元数据,从而知道如何组装应用。
而 Nest 中真正的装饰器模式应用 是 拦截器(Interceptor) 和 守卫(Guard),它们会在运行时包裹路由处理函数,动态增加日志、权限等功能。这部分我们会在后续文章中深入。
六、从 main.ts 看应用启动流程
typescript
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
流程拆解:
NestFactory.create(AppModule)------ 工厂创建应用实例。- 内部会读取
AppModule的元数据,递归解析所有依赖模块(如TodosModule)。 - 初始化所有控制器和服务,建立路由映射。
- 启动底层 HTTP 服务器,监听指定端口。
- 此时,我们的 API 就可以被访问了。
七、小结
| 概念 | 对应笔记/代码 | 我的理解 |
|---|---|---|
| 工厂模式 | 蜜雪冰城、NestFactory | 封装对象创建,解耦调用方与实现类 |
| 模块 | @Module({imports, controllers, providers}) | 组织代码的单元,类似公司部门 |
| 控制器 | @Controller,负责路由和参数提取 | 只做调度,不做业务 |
| 服务 | @Injectable,负责业务逻辑 | 可被注入到任何需要的地方 |
| 依赖注入 | constructor(private readonly xxxService) | 容器自动提供依赖,无需手动 new |
| 装饰器 | @符号,提供元数据 | 给类/方法打标签,供框架读取 |