2024年TypeScript类扩展/包装/拦截/装饰的解耦方案
实现TypeScript类的关注点分离:添加额外行为的原生方案
以下是2024年推荐的TypeScript原生方案,可通过扩展、包装、拦截或装饰类来分离业务逻辑与日志、遥测等额外行为:
1. TypeScript 稳定装饰器(推荐)
TypeScript 5.2+已支持ECMAScript标准装饰器,可通过方法装饰器批量为类方法注入额外逻辑,完全不侵入业务代码。
步骤:
- 定义通用遥测装饰器:
import { trace } from '@opentelemetry/api'; // 方法装饰器:为目标方法添加OpenTelemetry追踪逻辑 function traceMethod(spanName: string, attributes?: Record<string, string>) { return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) { const originalMethod = descriptor.value; // 替换原始方法为包裹了遥测逻辑的新方法 descriptor.value = async function(...args: any[]) { return trace.startActiveSpan(spanName, async (span) => { // 设置span属性 attributes && Object.entries(attributes).forEach(([key, val]) => span.setAttribute(key, val)); try { // 执行原始业务方法 return await originalMethod.apply(this, args); } finally { // 确保span结束 span.end(); } }); }; return descriptor; }; }
- 业务类仅添加装饰器,保持逻辑纯净:
class MyService{ @traceMethod('getSomething', { "specific to getSomething": "1234" }) public async getSomething(): Promise<string> { return "Hello World!"; } @traceMethod('getSomethingElse', { "specific to getSomethingElse": "ELSE" }) public async getSomethingElse(): Promise<string> { return "Hello Something else"; } @traceMethod('longRunningTask', { "GUID": "1234" }) public async longRunningTask(): Promise<string> { let r1 = await this.getSomething(); console.log(r1); let r2 = await this.getSomethingElse(); console.log(r2); return "All done" } }
优势:代码简洁、侵入性极低,装饰器可复用,符合关注点分离原则。
2. Proxy 代理拦截
利用JavaScript原生Proxy对象,动态拦截类实例的方法调用,在不修改原始类的前提下添加额外行为。
实现:
import { trace } from '@opentelemetry/api'; // 创建带遥测的代理实例工厂函数 function createTelemetryProxy<T extends object>(instance: T, spanConfig: Record<string, { name: string, attributes?: Record<string, string> }>): T { return new Proxy(instance, { get(target, prop: string) { const original = target[prop as keyof T]; // 仅拦截配置了遥测的方法 if (typeof original === 'function' && spanConfig[prop]) { return async (...args: any[]) => { const { name, attributes } = spanConfig[prop]; return trace.startActiveSpan(name, async (span) => { attributes && Object.entries(attributes).forEach(([key, val]) => span.setAttribute(key, val)); try { return await (original as Function).apply(target, args); } finally { span.end(); } }); }; } return original; } }); } // 使用方式 const rawService = new MyService(); const telemetryService = createTelemetryProxy(rawService, { getSomething: { name: 'getSomething', attributes: { "specific to getSomething": "1234" } }, getSomethingElse: { name: 'getSomethingElse', attributes: { "specific to getSomethingElse": "ELSE" } }, longRunningTask: { name: 'longRunningTask', attributes: { "GUID": "1234" } } });
优势:完全无侵入,支持运行时动态配置,可拦截所有方法(包括后续动态添加的)。
3. 继承 + 方法重写
与传统面向对象语言逻辑一致,通过继承业务类并重写目标方法,在重写逻辑中添加遥测后调用父类方法。
实现:
import { trace } from '@opentelemetry/api'; class MyServiceWithTelemetry extends MyService { public async getSomething(): Promise<string> { return trace.startActiveSpan('getSomething', async (span) => { span.setAttribute("specific to getSomething", "1234"); try { // 调用父类业务方法 return await super.getSomething(); } finally { span.end(); } }); } public async getSomethingElse(): Promise<string> { return trace.startActiveSpan('getSomethingElse', async (span) => { span.setAttribute("specific to getSomethingElse", "ELSE"); try { return await super.getSomethingElse(); } finally { span.end(); } }); } public async longRunningTask(): Promise<string> { return trace.startActiveSpan('longRunningTask', async (span) => { span.setAttribute("GUID", "1234"); try { return await super.longRunningTask(); } finally { span.end(); } }); } }
优势:逻辑直观,适合需要对单个方法做精细定制的场景;缺点:方法较多时代码冗余。
4. 高阶类包装器
通过高阶函数动态生成包装类,批量为原始类的方法注入额外逻辑,避免手动重写每个方法。
实现:
import { trace } from '@opentelemetry/api'; // 高阶函数:接收原始类,返回带遥测的包装类 function withTelemetry<T extends new (...args: any[]) => any>( BaseClass: T, spanConfig: Record<string, { name: string, attributes?: Record<string, string> }> ) { return class extends BaseClass { constructor(...args: any[]) { super(...args); // 批量包装配置的方法 for (const prop of Object.keys(spanConfig)) { const original = this[prop]; if (typeof original === 'function') { this[prop] = async (...args: any[]) => { const { name, attributes } = spanConfig[prop]; return trace.startActiveSpan(name, async (span) => { attributes && Object.entries(attributes).forEach(([key, val]) => span.setAttribute(key, val)); try { return await original.apply(this, args); } finally { span.end(); } }); }; } } } }; } // 使用方式 const MyServiceWithTelemetry = withTelemetry(MyService, { getSomething: { name: 'getSomething', attributes: { "specific to getSomething": "1234" } }, getSomethingElse: { name: 'getSomethingElse', attributes: { "specific to getSomethingElse": "ELSE" } }, longRunningTask: { name: 'longRunningTask', attributes: { "GUID": "1234" } } }); const service = new MyServiceWithTelemetry();
优势:批量处理方法,代码复用性高,无需手动维护继承关系。
内容的提问来源于stack exchange,提问作者Samuel
相关产品推荐
相关产品推荐

