Похоже, что в TypeScript это абсолютно нормально (с точки зрения компилятора), чтобы иметь такой код:
class Vehicle {
public run(): void { console.log('Vehicle.run'); }
}
class Task {
public run(): void { console.log('Task.run'); }
}
function runTask(t: Task) {
t.run();
}
runTask(new Task());
runTask(new Vehicle());
Но в то же время я ожидал бы ошибки компиляции, потому что Vehicle
и Task
не имеют ничего общего.
И разумные способы использования могут быть реализованы с помощью явного определения интерфейса:
interface Runnable {
run(): void;
}
class Vehicle implements Runnable {
public run(): void { console.log('Vehicle.run'); }
}
class Task implements Runnable {
public run(): void { console.log('Task.run'); }
}
function runRunnable(r: Runnable) {
r.run();
}
runRunnable(new Task());
runRunnable(new Vehicle());
... или общий родительский объект:
class Entity {
abstract run(): void;
}
class Vehicle extends Entity {
public run(): void { console.log('Vehicle.run'); }
}
class Task extends Entity {
public run(): void { console.log('Task.run'); }
}
function runEntity(e: Entity) {
e.run();
}
runEntity(new Task());
runEntity(new Vehicle());
И да, для JavaScript это абсолютно нормально, чтобы иметь такое поведение, потому что нет классов и никакого компилятора вообще (только синтаксический сахар) и утиная типизация является естественным для языка. Но TypeScript пытается ввести статические проверки, классы, интерфейсы и т.д. Однако утиная типизация для экземпляров классов выглядит довольно запутанной и подверженной ошибкам, на мой взгляд.