I am faced with a situation where I have two classes that rely on each other's methods:
class City {
(...)
mayor(): Person {
return this.people[0];
}
}
class Person {
(...)
birthCity(): City {
return this.cities.birth;
}
}
The challenge now is to create both mutable and immutable versions of these classes, where mutables return mutables and immutables return immutables, as shown below:
class MutableCity {
(...)
mayor(): MutablePerson {
return this.people[0];
}
// additional methods for mutations
}
class ImmutableCity {
(...)
mayor(): ImmutablePerson {
return this.people[0];
}
}
This same concept applies to the Person
class as well.
My initial plan was to implement a generic abstract class for both City
and Person
, where the mutable and immutable classes could inherit from and specify the return types using a type argument:
class AbstractCity<PersonType extends AbstractPerson> {
readonly people: PersonType[];
constructor(startingPeople: PersonType[]) {
this.people = startingPeople;
}
mayor(): PersonType {
return this.people[0];
}
}
class ImmutableCity extends AbstractCity<ImmutablePerson> {}
class MutableCity extends AbstractCity<MutablePerson> {
electNewbornInfant() { // example of additional method
this.people.unshift(
new MutablePerson(this)
);
}
}
class AbstractPerson<CityType extends AbstractCity> {
readonly cities: {
birth: CityType,
favorite?: CityType,
};
constructor(birthCity: CityType) {
this.cities = {
birth: birthCity
};
}
birthCity(): CityType {
return this.cities.birth;
}
}
class ImmutablePerson extends AbstractPerson<ImmutableCity> {}
class MutablePerson extends AbstractPerson<MutableCity> {
chooseFavorite(favoriteCity: MutableCity) {
this.cities.favorite = favoriteCity;
}
}
However, the abstract classes need each other as type arguments, creating a challenge:
Generic type 'AbstractCity<PersonType>' requires 1 type argument(s).ts(2314)
Generic type 'AbstractPerson<CityType>' requires 1 type argument(s).ts(2314)
Nesting type arguments infinitely is not a viable solution:
class AbstractCity<
PersonType extends AbstractPerson<
CityType extends AbstractCity<
PersonType extends AbstractPerson<
CityType extends AbstractCity<
(etc.)
>
>
>
>
>
How can I resolve this issue with the types depending on each other? Or is there an alternative solution?
Some solutions that have been considered but were not suitable:
- Using
AbstractCity<PersonType>
instead of
removes information about the methods that can be called onAbstractCity<PersonType extends AbstractPerson>
PersonType
. - Rewriting methods in each mutable/immutable class with different return types would cause duplication of read-only methods.
It should be noted that the classes also have methods that return their own type, which has been achieved using the polymorphic this
instead of generics.