在不使用类型转换的情况下使用多态 this 在打字稿中实现类型安全克隆
Implementing typesafe clone in typescript using polymorphic this without using typecasts
在pull request for adding polymorphic this, one use case given is a clone method. However, when I attempted an implementation of this, I hit the same problem as shelby3 noted in the comment chain中:
how can polymorphic clone(): this be implemented if it is not legal to assign an instance of the subclass to this??? It seems instead the compiler will need to deduce that inheritance from Entity types clone() as an abstract method even though the base has an implementation, so as to insure that each subclass provides a unique implementation.".
对此有解决方案吗,还是我只需要使用强制类型转换来强制打字稿编译此类代码,并希望任何子类不要忘记自己实现克隆?
看这个例子,如果没有 <this>
转换,代码将无法编译。它还未能检测到 c.clone()
的类型错误:
interface Cloneable {
clone() : this
}
class ClassA implements Cloneable {
a: number = 0;
clone() : this {
let result = new ClassA();
result.a = this.a;
return <this>result;
}
}
class ClassB extends ClassA {
b: number = 0;
clone() : this {
let result = new ClassB();
result.a = this.a;
result.b = this.b;
return <this>result;
}
}
class ClassC extends ClassB {
c: number = 0;
// missing clone method
}
function test() {
const a = new ClassA();
const b = new ClassB();
const c = new ClassC();
const aClone = a.clone(); // aClone : ClassA
const bClone = b.clone(); // bClone : ClassB
const cClone = c.clone(); // typescript thinks cClone is ClassC, but is actually ClassB
alert(cClone.constructor.name); // outputs ClassB
}
test();
我同意你的看法,没有明显的类型安全方法来覆盖 clone()
或任何 returns 依赖于多态 this
.
的方法
多态 this
就像一个泛型类型参数,只有当您拥有 class 的具体实例时才会指定。与泛型类型参数一样,编译器很难在仍未指定时验证对此类类型的可分配性,例如在此类函数的实现中:
function similar<This extends { x: string }>(): This {
return { x: "" }; // error
}
以上是(正确的)错误,因为编译器不确定 {x: ""}
是否实际上可以分配给后来指定的 This
。如果你想让上面的编译没有错误,你需要使用类型断言。
这基本上就是你在每个 class 中实现 clone()
时必须做的事情,并且使用这样的类型断言可以抑制错误,以换取有人会来的非常真实的可能性稍后(使用 subclass)并违反您声明的类型。
那么,可以做什么呢?我还没有发现任何让我感觉很棒的东西。一切都像是一种解决方法。最 hackiest 的解决方法是通过检查基础 class 的构造函数来恢复一些 运行时 安全,如下所示:
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;
}
}
由于您知道每个 subclass 实例最终都会调用基础 class 构造函数,因此您的示例代码将出现以下运行时错误:
Error: HEY YOU! ClassC is MISSING a clone() implementation! FIX THAT!!
所以,只要有一些运行时测试,这至少给了任何潜在的 subclass 实施者一个注意到正在发生的事情的战斗机会。编译时类型安全当然更好,所以♂️
另一种方法是尝试提出 clone()
的基本 class 实现,它具有多态性并且不需要被子classed。这仍然有点脆弱,因为某些 subclass 可能会做一些奇怪的事情,而 base class 的 clone()
实现没有预料到,或者 subclass可以继续覆盖我们不想要的 clone()
(我们不能用 final
...fun read about that 之类的东西来阻止它)但它至少适用于您的示例代码:
class Clonable {
clone(): this {
const ret: this = Object.create(this.constructor.prototype);
Object.assign(ret, this);
return ret;
}
}
我们将 Clonable
设为基础 class 而不是接口,因为它附带了实现。然后你的 subclasses 对 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
这大概是我能做的最好的了。希望能帮助到你;祝你好运!
在pull request for adding polymorphic this, one use case given is a clone method. However, when I attempted an implementation of this, I hit the same problem as shelby3 noted in the comment chain中:
how can polymorphic clone(): this be implemented if it is not legal to assign an instance of the subclass to this??? It seems instead the compiler will need to deduce that inheritance from Entity types clone() as an abstract method even though the base has an implementation, so as to insure that each subclass provides a unique implementation.".
对此有解决方案吗,还是我只需要使用强制类型转换来强制打字稿编译此类代码,并希望任何子类不要忘记自己实现克隆?
看这个例子,如果没有 <this>
转换,代码将无法编译。它还未能检测到 c.clone()
的类型错误:
interface Cloneable {
clone() : this
}
class ClassA implements Cloneable {
a: number = 0;
clone() : this {
let result = new ClassA();
result.a = this.a;
return <this>result;
}
}
class ClassB extends ClassA {
b: number = 0;
clone() : this {
let result = new ClassB();
result.a = this.a;
result.b = this.b;
return <this>result;
}
}
class ClassC extends ClassB {
c: number = 0;
// missing clone method
}
function test() {
const a = new ClassA();
const b = new ClassB();
const c = new ClassC();
const aClone = a.clone(); // aClone : ClassA
const bClone = b.clone(); // bClone : ClassB
const cClone = c.clone(); // typescript thinks cClone is ClassC, but is actually ClassB
alert(cClone.constructor.name); // outputs ClassB
}
test();
我同意你的看法,没有明显的类型安全方法来覆盖 clone()
或任何 returns 依赖于多态 this
.
多态 this
就像一个泛型类型参数,只有当您拥有 class 的具体实例时才会指定。与泛型类型参数一样,编译器很难在仍未指定时验证对此类类型的可分配性,例如在此类函数的实现中:
function similar<This extends { x: string }>(): This {
return { x: "" }; // error
}
以上是(正确的)错误,因为编译器不确定 {x: ""}
是否实际上可以分配给后来指定的 This
。如果你想让上面的编译没有错误,你需要使用类型断言。
这基本上就是你在每个 class 中实现 clone()
时必须做的事情,并且使用这样的类型断言可以抑制错误,以换取有人会来的非常真实的可能性稍后(使用 subclass)并违反您声明的类型。
那么,可以做什么呢?我还没有发现任何让我感觉很棒的东西。一切都像是一种解决方法。最 hackiest 的解决方法是通过检查基础 class 的构造函数来恢复一些 运行时 安全,如下所示:
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;
}
}
由于您知道每个 subclass 实例最终都会调用基础 class 构造函数,因此您的示例代码将出现以下运行时错误:
Error: HEY YOU! ClassC is MISSING a clone() implementation! FIX THAT!!
所以,只要有一些运行时测试,这至少给了任何潜在的 subclass 实施者一个注意到正在发生的事情的战斗机会。编译时类型安全当然更好,所以♂️
另一种方法是尝试提出 clone()
的基本 class 实现,它具有多态性并且不需要被子classed。这仍然有点脆弱,因为某些 subclass 可能会做一些奇怪的事情,而 base class 的 clone()
实现没有预料到,或者 subclass可以继续覆盖我们不想要的 clone()
(我们不能用 final
...fun read about that 之类的东西来阻止它)但它至少适用于您的示例代码:
class Clonable {
clone(): this {
const ret: this = Object.create(this.constructor.prototype);
Object.assign(ret, this);
return ret;
}
}
我们将 Clonable
设为基础 class 而不是接口,因为它附带了实现。然后你的 subclasses 对 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
这大概是我能做的最好的了。希望能帮助到你;祝你好运!