如何实现互相返回对方类型的可变/不可变子类?
解决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
相关产品推荐
相关产品推荐

