JavaScript中的依赖注入:完整指南
依赖注入(Dependency Injection, DI)是一种强大的设计模式,是控制反转(IoC)原则的基础。对象不再自行创建其依赖项,而是由DI从外部"注入"它们。这使得代码更加灵活、可测试且易于维护。在本文中,我们将通过简单直观的示例,讲解如何在JavaScript和Node.js中实现依赖注入。
什么是依赖注入,为什么需要它?
假设您有一个与数据库交互的UserService类。如果不使用DI,您很可能会在服务内部直接创建数据库实例:
class UserService { constructor() { this.db = new Database(); // 紧耦合 }
getUser(id) { return this.db.findUser(id); }}这段代码违反了依赖倒置原则(Dependency Inversion Principle)。在测试UserService时,如果不修改类代码,就无法将真实数据库替换为模拟对象(mock)。DI解决了这个问题:
class UserService { constructor(database) { this.db = database; // 依赖从外部传入 }
getUser(id) { return this.db.findUser(id); }}
// 使用方式const db = new Database();const userService = new UserService(db);DI的主要优势:
- 可测试性: 可以轻松地将依赖替换为模拟对象(mocks)或桩(stubs)。
- 低耦合: 类依赖于抽象(接口),而非具体实现。
- 灵活性: 通过替换实现,可以轻松改变系统的行为。
- 代码复用: 组件变得独立,可以在不同上下文中使用。
JavaScript中依赖注入的方式
在JavaScript中,主要有三种依赖注入的方式:
1. 构造函数注入(Constructor Injection)
最常用且推荐的方式。依赖在创建对象时传入。
class Logger { log(message) { console.log(`[LOG]: ${message}`); }}
class OrderService { constructor(logger) { this.logger = logger; }
createOrder(data) { this.logger.log('新订单已创建'); // 创建订单的逻辑 }}
const logger = new Logger();const orderService = new OrderService(logger);orderService.createOrder({ product: 'Laptop', price: 1500 });2. Setter注入(Setter Injection)
在对象创建后,通过setter方法设置依赖。适用于可选的依赖。
class EmailService { constructor() { this.transporter = null; }
setTransporter(transporter) { this.transporter = transporter; }
send(email) { if (!this.transporter) { throw new Error('Transporter未设置'); } this.transporter.sendMail(email); }}
const emailService = new EmailService();emailService.setTransporter(someMailTransporter);3. 接口注入(Interface Injection — 条件性)
JavaScript没有原生接口,但可以使用TypeScript或文档来描述契约。在实践中,这归结为检查方法是否存在。
在Node.js中创建简单的DI容器
在大型应用中,为了方便管理依赖,使用DI容器非常有用。它存储所有依赖并自动解析它们。以下是一个最小实现:
class Container { constructor() { this.services = new Map(); this.singletons = new Map(); }
// 注册服务的工厂方法 register(name,