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

如何实现互相返回对方类型的可变/不可变子类?

解决TypeScript中互相依赖的泛型抽象类问题

问题场景

现有两个互相引用的类 City 和 Person,其方法返回对方类型:

class City {
  // ...其他代码
  mayor(): Person {
    return this.people[0];
  }
}

class Person {
  // ...其他代码
  birthCity(): City {
    return this.cities.birth;
  }
}

需要为每个类创建**可变(Mutable)和不可变(Immutable)**版本,要求:

  • 可变类的方法返回可变类型
  • 不可变类的方法返回不可变类型
  • 避免只读方法的代码重复

尝试的方案及问题

通过泛型抽象类复用逻辑,让可变/不可变类继承抽象类并指定返回类型,但遇到了循环泛型依赖的报错:

Generic type 'AbstractCity' requires 1 type argument(s).ts(2314)
Generic type 'AbstractPerson' requires 1 type argument(s).ts(2314)

无法通过无限嵌套泛型参数解决问题,同时也不想放弃类型约束或重复编写方法。

解决方案:双向约束的泛型+接口契约

通过先定义互相引用的接口契约,再让抽象类实现接口,同时使用双向约束的泛型参数,解决循环依赖问题,同时保留类型安全和代码复用。

步骤1:定义互相约束的接口

先声明接口来定义类的契约,接口可以合法地互相引用:

// 定义Person的接口契约,约束City类型
interface IPerson<
  CityType extends ICity<PersonType, CityType>,
  PersonType extends IPerson<CityType, PersonType>
> {
  cities: { birth: CityType; favorite?: CityType };
  birthCity(): CityType;
}

// 定义City的接口契约,约束Person类型
interface ICity<
  PersonType extends IPerson<CityType, PersonType>,
  CityType extends ICity<PersonType, CityType>
> {
  people: PersonType[];
  mayor(): PersonType;
}

步骤2:实现泛型抽象类

基于接口实现抽象类,复用核心逻辑:

// Person的抽象基类
abstract class AbstractPerson<
  CityType extends ICity<PersonType, CityType>,
  PersonType extends IPerson<CityType, PersonType>
> implements IPerson<CityType, PersonType> {
  cities: { birth: CityType; favorite?: CityType };

  constructor(birthCity: CityType) {
    this.cities = { birth: birthCity };
  }

  birthCity(): CityType {
    return this.cities.birth;
  }
}

// City的抽象基类
abstract class AbstractCity<
  PersonType extends IPerson<CityType, PersonType>,
  CityType extends ICity<PersonType, CityType>
> implements ICity<PersonType, CityType> {
  people: PersonType[];

  constructor(startingPeople: PersonType[]) {
    this.people = startingPeople;
  }

  mayor(): PersonType {
    return this.people[0];
  }
}

步骤3:实现可变/不可变类

基于抽象类创建具体的可变和不可变类,各自扩展专属方法:

不可变版本(属性只读)

class ImmutablePerson extends AbstractPerson<ImmutableCity, ImmutablePerson> {
  // 标记属性为只读,确保不可修改
  readonly cities: { birth: ImmutableCity; favorite?: ImmutableCity };
}

class ImmutableCity extends AbstractCity<ImmutablePerson, ImmutableCity> {
  readonly people: ImmutablePerson[];
}

可变版本(支持修改操作)

class MutablePerson extends AbstractPerson<MutableCity, MutablePerson> {
  // 新增修改方法
  chooseFavorite(favoriteCity: MutableCity) {
    this.cities.favorite = favoriteCity;
  }
}

class MutableCity extends AbstractCity<MutablePerson, MutableCity> {
  // 新增修改方法
  electNewbornInfant() {
    this.people.unshift(new MutablePerson(this));
  }
}

验证类型安全

此时调用方法会返回正确的类型:

const immutableCity = new ImmutableCity([new ImmutablePerson(immutableCity)]);
const immutableMayor = immutableCity.mayor(); // 类型为 ImmutablePerson
const immutableBirthCity = immutableMayor.birthCity(); // 类型为 ImmutableCity

const mutableCity = new MutableCity([new MutablePerson(mutableCity)]);
const mutableMayor = mutableCity.mayor(); // 类型为 MutablePerson
const mutableBirthCity = mutableMayor.birthCity(); // 类型为 MutableCity

补充说明

  • 这种方案通过接口的前向引用解决了循环依赖问题,同时泛型参数的双向约束保证了类型安全
  • 抽象类复用了所有核心只读方法的逻辑,避免了代码重复
  • 可变类和不可变类可以各自扩展专属的修改方法,互不影响
  • 若需要返回自身类型的方法,你已实现的多态this依然可以正常使用,因为抽象类的泛型参数已绑定具体子类类型,不会产生类型冲突。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.20 06:39:36