鉴于您的示例代码,
class CarWheelHubcap { }
class CarWheel extends Wheel {
hubcap = new CarWheelHubcap();
}
然后继续到您遇到错误的地方,
const car = new Car();
car.wheels().forEach(wheel => wheel.hubcap);
Error: Property 'hubcap' does not exist on type 'Wheel'.
正如错误所说,Wheel 没有定义名为 hubcap 的成员。
您尝试重新声明具有更具体类型的Car 中的_wheels 属性(即影子)的方法存在许多问题。一方面,它不是类型安全的,因为数组是可变的。另一方面,实际错误源于wheels() 方法的返回类型是从Vehicle 类中属性_wheels 的类型推断出来的,这让您陷入编写额外的、无意义的代码或编写复杂的计算在这种相对简单的情况下不需要的类型注释。
相反,我们将利用Parametric Polymorphism。这个概念在 TypeScript 中作为泛型公开,而你
可以在TypeScript: Handbook - Generics 部分了解它们。
看来你想表达Car继承Vehicle.prototype,确实有轮子,但不只是Wheels,而是CarWheels。继承不足以表达这种双轴关系,我们需要使用泛型来表达和加强这种关系。
让我们先试一试:
class Vehicle<TWheel extends Wheel> {
abstract _wheels: TWheel[];
wheels() {
return this._wheels; // assume it's more complex than this
}
}
我们所做的是使用类型参数 TWheel 参数化 Vehicle,它指定它将具有哪些轮子。此外,我们规定为类型参数TWheel 提供的任何类型参数都必须扩展Wheel。
现在在定义Car 时,我们必须为TWheel 传递一个类型参数
class Car extends Vehicle<CarWheel> {
_wheels: CarWheel[] = [];
}
注意:我删除了多余的空构造函数,将其替换为字段初始值设定项。这相当于constructor() {super(); this._wheels = [];},同时更易于阅读和维护。
但是,由于 _wheels 数组的元素类型在我们扩展它时被传递给 Vehicle<TWheel>,我们可以通过在车辆中编写从 _wheels 中删除 abstract 修饰符来进一步改进代码,实现那里的数组并消除了在子类中定义它的任何需要。
class Vehicle<TWheel extends Wheel> {
_wheels: TWheel[] = [];
wheels() {
return this._wheels; // assume it's more complex than this
}
}
这将Car 简化为
class Car extends Vehicle<CarWheel>{ }
使用Car 参数化Vehicle 和CarWheel,您的原始代码现在可以工作了。
car.wheels().forEach(wheel => wheel.hubcap);
以上方法现在有效,因为 Car 的轮子已知为 CarWheel
注意:为了保持与现有代码的兼容性,并允许没有专用车轮的车辆简单而简洁地扩展车辆,我们可以为我们的类型参数指定一个默认值。因此,如果说另一个类,比如代表装甲车,不使用专门的轮子,我们可以写class Armor extends Vehicle { }。
class Vehicle<TWheel extends Wheel = Wheel> {
_wheels: TWheel[] = [];
wheels() {
return this._wheels;
}
}
最后,让我们对Vehicle 进行修改,使其更加地道。 wheels 不需要是方法,我们可以使用 get 属性访问器。
class Vehicle<TWheel extends Wheel = Wheel> {
get wheels() { return this._wheels; }
}
这让消费者在编写car.wheels.forEach(...) 时无需考虑封装技术(“哦,这是一种包装方法”),这是他们不必担心的问题。
放在一起:
class Wheel {
diameter = 30;
}
class Vehicle<TWheel extends Wheel = Wheel> {
_wheels: TWheel[] = [];
get wheels() {
return this._wheels; // assume it's more complex than this
}
}
class CarWheelHubcap {
size = 15;
}
class CarWheel extends Wheel {
hubcap = new CarWheelHubcap();
}
class Car extends Vehicle<CarWheel> {
drivetrain: 'AWD' | 'FWD' | 'RWD' = 'AWD';
}
const car = new Car();
car.wheels
.map(wheel => wheel.hubcap)
.forEach(console.log);
Playground Link
请注意,我已为每个类添加了一个成员,因为空类是一种糟糕的做法,可能会导致错误并引起混乱和混乱。
此外,如果我们需要一个带有轮子数组的构造函数,参数多态性解决方案仍然可以很好地使用。我们可以简单地向Vehicle 添加一个带轮子的构造函数,所有子类都将公开一个适当类型的构造函数,需要指定TWheel 的数组。 Playground Link