TokenType 的使用场景
问题背景
在依赖注入中,最直接的写法是让一个类直接 import 并依赖另一个类:
ts
// OrderService.ts
import { PaymentService } from './PaymentService';
import { Inject, Injectable } from '@kaokei/di';
@Injectable()
class OrderService {
@Inject(PaymentService)
public paymentService!: PaymentService;
}这样写的问题是 OrderService 强制依赖了 PaymentService 的具体实现。如果将来想替换支付服务的实现(比如从信用卡支付换成微信支付),就必须修改 OrderService 的代码。
解决方案:使用 Token + TokenType 解耦
核心思路是引入一个独立的 token.ts 文件,让服务之间通过 Token 间接依赖,而不是直接 import 对方。
文件结构
src/
├── token.ts # 集中定义所有 Token
├── PaymentService.ts # 支付服务(被依赖方)
├── OrderService.ts # 订单服务(依赖方)
└── index.ts # 组装容器,绑定 Token 和实现第 1 步:定义 PaymentService(被依赖方)
PaymentService 是一个独立的服务,不需要 import 任何其他业务服务。
ts
// PaymentService.ts
import { Injectable } from '@kaokei/di';
@Injectable()
export class PaymentService {
method = 'credit_card';
pay(amount: number): string {
const result = `通过 ${this.method} 支付了 ${amount} 元`;
console.log(result);
return result;
}
refund(orderId: string, amount: number): string {
const result = `订单 ${orderId} 退款 ${amount} 元`;
console.log(result);
return result;
}
}第 2 步:定义 Token(解耦的关键)
token.ts 是集中管理所有 Token 的地方。这里使用 Token 创建标识符,使用 TokenType 导出对应的类型别名。
ts
// token.ts
import { Token, type TokenType } from '@kaokei/di';
import type { OrderService } from './OrderService';
import type { PaymentService } from './PaymentService';
// 订单服务的 Token
export const tokenOrderService = new Token<OrderService>('OrderService');
// TokenType<typeof tokenOrderService> 等价于 OrderService 类型
export type TokenOrderService = TokenType<typeof tokenOrderService>;
// 支付服务的 Token
export const tokenPaymentService = new Token<PaymentService>('PaymentService');
// TokenType<typeof tokenPaymentService> 等价于 PaymentService 类型
export type TokenPaymentService = TokenType<typeof tokenPaymentService>;注意这里 import 的是 type,这意味着在运行时不会产生实际的 import,不会引入模块间的运行时依赖。
第 3 步:定义 OrderService(依赖方)
OrderService 依赖支付功能,但它不直接 import PaymentService,而是通过 Token 来声明依赖。
ts
// OrderService.ts
import { Inject, Injectable, type TokenType } from '@kaokei/di';
import { tokenPaymentService, type TokenPaymentService } from './token';
@Injectable()
export class OrderService {
// 使用 Token 注入,而不是直接 @Inject(PaymentService)
@Inject(tokenPaymentService)
public paymentService!: TokenPaymentService;
createOrder(orderId: string, amount: number): void {
console.log(`创建订单:${orderId},金额:${amount} 元`);
// 调用支付服务,这里有完整的类型提示
this.paymentService.pay(amount);
console.log(`订单 ${orderId} 创建成功`);
}
cancelOrder(orderId: string, amount: number): void {
console.log(`取消订单:${orderId}`);
this.paymentService.refund(orderId, amount);
console.log(`订单 ${orderId} 已取消`);
}
}第 4 步:组装容器
index.ts 是唯一需要同时知道 Token 和具体实现类的地方。
ts
// index.ts
import { Container } from '@kaokei/di';
import { tokenOrderService, tokenPaymentService } from './token';
import { OrderService } from './OrderService';
import { PaymentService } from './PaymentService';
const container = new Container();
// 将 Token 绑定到具体的实现类
container.bind(tokenOrderService).to(OrderService);
container.bind(tokenPaymentService).to(PaymentService);
// 通过 Token 获取服务实例
const orderService = container.get(tokenOrderService);
orderService.createOrder('ORD-001', 299);
orderService.cancelOrder('ORD-001', 299);TokenType 的作用
TokenType 是一个工具类型,用于从 Token 实例中提取泛型参数的类型。
ts
const tokenPaymentService = new Token<PaymentService>('PaymentService');
// 以下两种写法等价:
type A = TokenType<typeof tokenPaymentService>; // PaymentService
type B = PaymentService; // PaymentService它的价值在于:你不需要直接 import PaymentService 类型,只需要 import tokenPaymentService 这个 Token,就能通过 TokenType 推导出正确的类型。这样在属性声明时既能获得完整的类型提示,又不会引入对具体实现的直接依赖。
对比总结
| 方面 | 直接依赖 | Token 解耦 |
|---|---|---|
| import 关系 | OrderService 直接 import PaymentService | OrderService 只 import token.ts |
| 注入写法 | @Inject(PaymentService) | @Inject(tokenPaymentService) |
| 类型声明 | paymentService!: PaymentService | paymentService!: TokenPaymentService |
| 替换实现 | 需要修改 OrderService | 只需修改 index.ts 中的绑定 |
| 类型安全 | ✅ | ✅ |
| 运行时解耦 | ❌ | ✅ |
适用场景
- 服务的实现可能会被替换(比如不同的支付方式、不同的数据库驱动)
- 需要在测试中 mock 依赖服务
- 多个模块之间需要降低耦合度
- 希望在
index.ts(组装层)统一管理依赖关系