Я согласен с вами, что не существует очевидного безопасного типа для переопределения clone()
или какого-либо метода, который возвращает что-либо, зависящее от polymorphi c this
.
Polymorphi c this
подобен универсальному параметру типа c, который указывается только тогда, когда у вас есть конкретный экземпляр класса. Как и параметр типа generi c, компилятору становится трудно проверить присвоение такого типа, когда он еще не указан, например, внутри реализации такой функции:
function similar<This extends { x: string }>(): This {
return { x: "" }; // error
}
(правильно) ошибка, потому что компилятор не уверен, будет ли {x: ""}
фактически назначаться тому, что позже будет указано This
. Если вы хотите, чтобы вышесказанное компилировалось без ошибок, вам нужно использовать утверждение типа.
Это в основном то, что вы должны делать при реализации clone()
в каждом классе, и использование такого утверждения типа подавляет ошибку в обмен на вполне реальную возможность того, что кто-то придет позже (с подкласс) и нарушить ваш заявленный тип.
Итак, что можно сделать? Я не нашел ничего, что мне нравится. Все похоже на обходной путь. Самый хакерский обходной путь - вернуть некоторую безопасность во время выполнения , установив проверку в конструкторе базового класса, например:
class ClassA implements Cloneable {
a: number = 0;
// add this constructor
constructor() {
if (!this.constructor.prototype.hasOwnProperty("clone")) {
throw new Error("HEY YOU! " + this.constructor.name +
" is MISSING a clone() implementation! FIX THAT!!")
}
}
clone(): this {
let result = new ClassA();
result.a = this.a;
return <this>result;
}
}
Поскольку вы знаете, что каждый экземпляр подкласса будет вызывать конструктор базового класса, вы получите следующую ошибку времени выполнения с вашим примером кода:
Error: HEY YOU! ClassC is MISSING a clone() implementation! FIX THAT!!
Так что, по крайней мере, любой потенциальный разработчик подкласса получит боевой шанс заметить, что происходит, пока есть некоторое тестирование во время выполнения. Конечно, безопасность типов времени компиляции была бы лучше, поэтому ?♂️
Другой подход - попытаться придумать реализацию базового класса clone()
, которая ведет себя полиморфно и не должны быть подклассами. Это все еще что-то вроде fr agile, в том смысле, что некоторый подкласс может сделать некоторые странные вещи, которые реализация clone()
базового класса не ожидала, или подкласс может go опередить и переопределить clone()
, что мы не делаем хочу (и мы не можем предотвратить это с помощью чего-то вроде final
... , забавно читать об этом ), но это по крайней мере работает для вашего примера кода:
class Clonable {
clone(): this {
const ret: this = Object.create(this.constructor.prototype);
Object.assign(ret, this);
return ret;
}
}
Мы делаем Clonable
базовый класс вместо интерфейса, так как реализация идет с ним. Тогда ваши подклассы ничего не делают для clone()
:
class ClassA extends Clonable {
a: number = 0;
}
class ClassB extends ClassA {
b: number = 0;
}
class ClassC extends ClassB {
c: number = 0;
}
И ваши тесты работают так, как вы хотите:
const c = new ClassC();
const cClone = c.clone(); // cClone : ClassC
alert(cClone.constructor.name); // outputs ClassC
Это лучшее, что я могу сделать. Надеюсь, поможет; удачи!
Детская площадка ссылка на код