TypeScript中的依赖注入:从理论到实践
依赖注入(DI)是一种关键的设计模式,它允许您创建灵活、可测试且可维护的应用程序。在TypeScript中,得益于强类型和装饰器的支持,实现DI变得尤为优雅。在本文中,我们将探讨如何在实际中运用TypeScript实现依赖注入,并了解为什么该模式是现代开发的基石。
什么是依赖注入,为什么需要它?
依赖注入是一种模式,其中对象从外部获取其依赖项,而不是自己创建它们。与其在类内部编写new SomeService(),不如通过构造函数或方法传递一个现成的实例。
DI的主要优势:
- 降低耦合度 — 类不依赖于具体实现,只依赖于抽象(接口)。
- 简化测试 — 我们可以轻松地用模拟对象或桩件替换真实服务。
- 提高可重用性 — 无需更改使用者代码即可替换依赖项。
- 遵循依赖反转原则(SOLID中的DIP) — 高层模块不依赖于低层模块。
在TypeScript中实现依赖注入(无需框架)
让我们从一个简单的例子开始。假设我们有一个日志记录服务和一个使用它的类。
// 没有DI - 紧密耦合class Logger { log(message: string): void { console.log(`[LOG]: ${message}`); }}
class UserService { private logger: Logger;
constructor() { this.logger = new Logger(); // 问题:紧密依赖 }
createUser(name: string): void { this.logger.log(`用户已创建:${name}`); // ... 创建逻辑 }}
// 使用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 { // 写入文件的逻辑 console.log(`[文件日志]: ${message}`); }}
class UserServiceWithDI { constructor(private logger: ILogger) {} // 依赖从外部注入
createUser(name: string): void { this.logger.log(`用户已创建:${name}`); }}
// 使用const consoleLogger = new ConsoleLogger();const userService = new UserServiceWithDI(consoleLogger);userService.createUser('Alice'); // [控制台日志]: 用户已创建:Alice如您所见,现在UserServiceWithDI不知道具体使用的是哪个日志记录器。我们可以轻松地将其替换为FileLogger或用于测试的模拟对象。
高级DI:创建简单的IoC容器
当依赖项很多时,通过构造函数手动注入会变得繁琐。这时就需要IoC容器(控制反转容器)。它管理对象的创建和生命周期。
让我们在TypeScript中实现一个简单的容器:
// 依赖工厂的类型type Factory<T> = (container: Container) => T;
class Container { private factories = new Map<string, Factory<any>>(); private instances = new Map<string, any>();
// 注册依赖(每次请求时调用工厂) register<T>(name: string, factory: Factory<T>): void { this.factories.set(name, factory); }
// 注册单例(工厂只调用一次) singleton<T>(name: string, factory: Factory<T&g