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

TypeScript如何实现仅支持同类型操作的Operable接口?

问题描述

我写了一段变量统计代码,核心逻辑重复多次,打算抽象封装。统计基于不可变值对象(当前是Money和Time),需要定义Operable接口实现抽象,目标是让这些值对象能接入以下Average泛型类:

class Average<T extends Operable> {
   private value: T;
   private count: number;

   public constructor(init: T) {
      this.value = init;
      this.count = 0;
   }

   public add(value: T): void {
      this.value = this.value.add(value);
      this.count++;
   }

   public getAverage(): T {
      return this.value.divide(Math.max(1, this.count));
   }
}

最初定义的Operable接口如下:

interface Operable {
   add(value: Operable): Operable;
   divide(value: number): Operable;
}

但这个接口存在两个问题:

  • 允许跨类型操作,比如const m = new Money(); const t = new Time(); m.add(t);这种非法操作无法被TypeScript拦截
  • 在Average.add方法中报错:

Type 'Operable' is not assignable to type 'T'.
'Operable' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'Operable'.

后来尝试用this类型优化接口:

interface Operable {
   add(value: this): this;
   divide(value: number): this;
}

这解决了Average类的报错,但值对象(比如Money)的实现又出现类型不兼容错误:

Property 'add' in type 'Money' is not assignable to the same property in base type 'Operable'.
Type '(money: Money) => Money' is not assignable to type '(value: this) => this'.
Type 'Money' is not assignable to type 'this'.
'Money' is assignable to the constraint of type 'this', but 'this' could be instantiated with a different subtype of constraint 'Money'.

Money和Time的具体定义:

class Money {
   private readonly amount: number;
   private readonly currency: string;

   public constructor(amount: number, currency: string) {
      this.amount = amount;
      this.currency = currency;
   }

   public add(value: Money): Money {
      return new Money(this.amount + value.amount, this.currency);
   }

   public divide(value: number): Money {
      return new Money(this.amount / value, this.currency);
   }
}

class Time {
   private readonly duration: number;

   public constructor(duration: number) {
      this.duration = duration;
   }

   public add(value: Time): Time {
      return new Time(this.duration + value.duration);
   }

   public divide(value: number): Time {
      return new Time(this.duration / value);
   }
}

请问在TypeScript中如何实现这种仅支持同类型操作的接口,适配不可变值对象场景?

解决方案

可以通过泛型接口+递归约束来实现,确保add方法只能接收同类型实例,同时解决所有类型兼容问题。

1. 定义泛型Operable接口

将Operable改为泛型接口,让每个实现类指定自身类型作为泛型参数,明确操作的类型边界:

interface Operable<T> {
  add(value: T): T;
  divide(value: number): T;
}

2. 修改值对象实现

让Money和Time实现对应泛型接口,将自身类型传入泛型参数:

class Money implements Operable<Money> {
   private readonly amount: number;
   private readonly currency: string;

   public constructor(amount: number, currency: string) {
      this.amount = amount;
      this.currency = currency;
   }

   public add(value: Money): Money {
      return new Money(this.amount + value.amount, this.currency);
   }

   public divide(value: number): Money {
      return new Money(this.amount / value, this.currency);
   }
}

class Time implements Operable<Time> {
   private readonly duration: number;

   public constructor(duration: number) {
      this.duration = duration;
   }

   public add(value: Time): Time {
      return new Time(this.duration + value.duration);
   }

   public divide(value: number): Time {
      return new Time(this.duration / value);
   }
}

3. 调整Average泛型类约束

修改Average类的泛型约束为T extends Operable<T>,通过递归约束确保T的add方法返回和接收的都是自身类型:

class Average<T extends Operable<T>> {
   private value: T;
   private count: number;

   public constructor(init: T) {
      this.value = init;
      this.count = 0;
   }

   public add(value: T): void {
      this.value = this.value.add(value);
      this.count++;
   }

   public getAverage(): T {
      return this.value.divide(Math.max(1, this.count));
   }
}

验证效果

现在可以正常使用,同时TypeScript会自动拦截跨类型操作:

// 合法操作
const moneyAvg = new Average(new Money(0, "USD"));
moneyAvg.add(new Money(10, "USD"));
console.log(moneyAvg.getAverage()); // Money { amount: 10, currency: "USD" }

const timeAvg = new Average(new Time(0));
timeAvg.add(new Time(60));
console.log(timeAvg.getAverage()); // Time { duration: 60 }

// 非法操作,TypeScript直接报错:类型Time的参数不能赋值给类型Money的参数
// moneyAvg.add(new Time(60));

这种方案既保证了值对象的同类型操作约束,又让Average类能正确进行类型推断,完美适配不可变值对象的场景。


内容的提问来源于stack exchange,提问作者carlosV2

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 06:13:13