TypeScript 中的依赖注入简介
依赖注入 (Dependency Injection, DI) 是一种关键的设计模式,它允许您创建松耦合、可测试且易于维护的应用程序。在 TypeScript 的上下文中,得益于强类型和装饰器的支持,实现 DI 变得特别优雅且安全。
在本文中,我们将探讨什么是依赖注入,如何在 TypeScript 中实现它,以及哪些工具可以简化这个过程。您将了解 DI 如何帮助消除类之间的硬编码耦合,并使您的代码更加灵活。
什么是依赖注入以及为什么需要它?
依赖注入 (Dependency Injection) 是一种模式,其中对象从外部获取其依赖项,而不是自己创建它们。这实现了 控制反转 (Inversion of Control, IoC) 原则。
没有 DI 的问题:
class Logger { log(message: string): void { console.log(message); }}
class UserService { private logger: Logger;
constructor() { this.logger = new Logger(); // 硬编码耦合 }
createUser(name: string): void { this.logger.log(`User ${name} created`); }}在这个例子中,UserService 被硬编码绑定到 Logger 的具体实现。如果我们想将日志记录器替换为文件日志记录器或远程日志记录器,就必须修改 UserService 的代码。
使用 DI 的解决方案:
interface ILogger { log(message: string): void;}
class ConsoleLogger implements ILogger { log(message: string): void { console.log(message); }}
class FileLogger implements ILogger { log(message: string): void { // 写入文件 }}
class UserService { constructor(private logger: ILogger) {} // 依赖项被注入
createUser(name: string): void { this.logger.log(`User ${name} created`); }}
// 使用const service = new UserService(new ConsoleLogger());DI 的优势:
- 可测试性 — 可以轻松地将真实依赖项替换为模拟对象 (mock objects)。
- 灵活性 — 可以在不修改类代码的情况下更改系统行为。
- 职责分离 — 类不负责创建自己的依赖项。
- 可重用性 — 依赖项可以在应用程序的不同部分使用。
在 TypeScript 中手动实现依赖注入
最简单的方法是通过构造函数或属性注入依赖项。TypeScript 凭借其接口和访问修饰符,使这变得非常方便。
通过构造函数注入 (Constructor Injection)
interface IRepository<T> { getAll(): Promise<T[]>; getById(id: number): Promise<T | null>;}
class UserRepository implements IRepository<User> { async getAll(): Promise<User[]> { // 与数据库交互的实际逻辑 return []; }
async getById(id: number): Promise<User | null> { return null; }}
class UserController { constructor( private repository: IRepository<User>, private logger: ILogger ) {}
async getUsers(): Promise<User[]> { this.logger.log('正在获取用户...'); return this.repository.getAll(); }}
// 创建依赖图const logger = new ConsoleLogger();const repository = new UserRepository();const controller = new UserController(repository, logger);通过属性注入 (Property Injection)
class EmailService { private sender!: ISender;
setSender(sender: ISender): void {