NodeJS+TypeScript MVC架构中,依赖注入构造函数设默认对象是否合规?
我正在使用NodeJS和TypeScript开发MVC模式的REST API。在模型层我创建了实体(entities)、服务(services)和仓库(repositories)。控制器层与框架解耦,通过适配器接收请求和响应对象。
我的控制器中有一个需通过构造函数注入的服务,但我创建了一个默认服务,以防未传入任何服务的情况:
import { type HttpResponse } from './Contracts/httpResponse'; import { type HttpRequest } from './Contracts/httpRequest'; import { HealthService } from '../Core/Service/Implementation/health.service'; import type { HealthServiceInterface } from '../Core/Service/Contract/health.service'; export class HealthController { private readonly healthService: HealthServiceInterface; constructor(healthService?: HealthServiceInterface) { this.healthService = healthService ?? new HealthService(); } public async getHealth(request: HttpRequest): Promise<HttpResponse> { const service = new HealthService(); const health = await service.getHealth(); return { statusCode: 200, body: health } } }
在服务层我也做了同样的处理:接收一个仓库作为参数,但如果参数未定义,我会实例化一个标准仓库:
import type { HealthRepositoryInterface } from "../../Repository/Contract/health.repository"; import type { HealthServiceInterface } from "../Contract/health.service"; import type { Health } from "../../Entity/health.entity"; import { HealthRepository } from "../../Repository/Implementation/health.repository"; export class HealthService implements HealthServiceInterface { private readonly healthRepository: HealthRepositoryInterface; constructor(healthRepository?: HealthRepositoryInterface) { this.healthRepository = healthRepository ?? new HealthRepository(); } public async getHealth(): Promise<Health> { const health: Health = await this.healthRepository.getHealth(); return health } }
我的问题是:这种实现方式是否正确?我是否存在某些架构错误?
你的核心分层思路是合理的,但存在几个关键细节问题需要修正:
1. 控制器中注入的服务未被实际使用
在HealthController的getHealth方法里,你重新实例化了new HealthService(),完全忽略了构造函数中注入(或默认创建)的this.healthService。这直接让依赖注入失去意义——比如你想在测试时注入Mock服务,这个方法根本不会用到它。
修正方案:直接使用类成员变量:
public async getHealth(request: HttpRequest): Promise<HttpResponse> { const health = await this.healthService.getHealth(); return { statusCode: 200, body: health } }
2. 构造函数硬编码默认实现的潜在问题
你在构造函数里用??兜底实例化具体实现(比如new HealthService()、new HealthRepository()),这种做法有明显的利弊:
- 优点:类可以独立实例化,不需要外部DI容器就能运行,降低了入门门槛。
- 缺点:
- 违反了依赖倒置原则的核心精神——高层模块(控制器/服务)依然直接依赖底层具体实现,而非完全依赖抽象。
- 如果后续需要替换默认实现,必须修改类的构造函数代码,不符合开闭原则。
- 测试时如果忘记传入Mock实例,会自动使用真实实现,可能导致测试依赖外部资源(比如数据库)。
优化建议
如果你的项目已经在使用DI容器(比如TypeDI、InversifyJS),建议移除构造函数中的默认实例化,强制通过DI容器注入依赖:
// 控制器构造函数改为必填参数 constructor(private readonly healthService: HealthServiceInterface) {} // 服务构造函数同理 constructor(private readonly healthRepository: HealthRepositoryInterface) {}
如果暂时不想引入DI容器,希望保持类的可独立运行性,可以把默认实现的创建逻辑抽离到类外部,比如通过静态工厂方法:
export class HealthController { private readonly healthService: HealthServiceInterface; private constructor(healthService: HealthServiceInterface) { this.healthService = healthService; } // 静态工厂方法提供默认实例 public static create(): HealthController { return new HealthController(new HealthService()); } // 用于测试或自定义注入的工厂方法 public static createWithService(healthService: HealthServiceInterface): HealthController { return new HealthController(healthService); } // ... getHealth方法 }
这种方式既保留了类的易用性,又避免了构造函数中硬编码具体实现的耦合问题。
3. 架构分层的合理性
你的模型层拆分了实体、服务、仓库,控制器层与框架解耦,这部分是符合MVC+DDD的分层思想的,没有架构层面的错误,继续保持即可。
内容的提问来源于stack exchange,提问作者Abner Matheus

