You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

2024年TypeScript类扩展/包装/拦截/装饰的解耦方案

实现TypeScript类的关注点分离:添加额外行为的原生方案

以下是2024年推荐的TypeScript原生方案,可通过扩展、包装、拦截或装饰类来分离业务逻辑与日志、遥测等额外行为:

1. TypeScript 稳定装饰器(推荐)

TypeScript 5.2+已支持ECMAScript标准装饰器,可通过方法装饰器批量为类方法注入额外逻辑,完全不侵入业务代码。

步骤:

  1. 定义通用遥测装饰器:
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;
  };
}
  1. 业务类仅添加装饰器,保持逻辑纯净:
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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.02 09:14:52