首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >在类型记录中创建通用"Cloneable“接口

在类型记录中创建通用"Cloneable“接口
EN

Stack Overflow用户
提问于 2021-03-05 19:56:28
回答 1查看 931关注 0票数 1

考虑一个类型记录接口,它描述了一个对象具有一个返回相同类型的对象(或者可能是一个可以分配给它自己的类型的对象)的.clone()方法的约束。我想象的一个例子:

代码语言:javascript
复制
// Naive definition
interface Cloneable {
  clone: () => Clonable;
}

class A implements Cloneable {
  a: number[];
  constructor(a: number[]) {
    this.a = a;
  }
  clone(): A {
    return new A(this.a.slice()); // returns a deep copy of itself
  }
}

interface RequiresCloneable<T extends Cloneable> {
  //...
}

const arrayOfCloneables: Clonable[] = [new A([1,2,3])];

虽然这是可行的,但是接口无法理解Cloneable应该如何运行:它允许一个类Faker,它的.clone()方法返回另一个类的克隆对象,这个对象是不可分配的,无法键入Faker。为了解决这个问题,我们可以按类型参数化Cloneable接口:

代码语言:javascript
复制
// Approach 1
interface Cloneable<T> {
  clone: () => T;
}
// Approach 2
interface Cloneable<T> {
  clone: () => Cloneable<T>;
}
// Approach 3. This pattern looks quite strange but this seems to work the best.
interface Cloneable<T extends Cloneable<T>> {
  clone: () => T;
}

然后,要将类型参数约束为cloneable,我们可以指定T extends Cloneable<T>

代码语言:javascript
复制
  // Interface that requires a cloneable
interface RequiresCloneable<T extends Cloneable<T>> {
  //...
}

这三个参数化接口都允许类A。然而,我们失去了在不知道其实现类型的情况下指定特定值应该是可克隆的能力:

代码语言:javascript
复制
const clonableA: Cloneable<A> = new A([1]);
const clonableOfUnknownType: Cloneable<unknown>; // admits Fakers with approach 1 and 2, doesn't compile with approach 3

我试着思考类型系统是如何工作的,以及在类型系统下什么是可能的,什么是不可能的。我的问题是:

  • 是否有任何方法来制作一个非通用的克隆接口或类型来捕捉对象的克隆应该与其自身相同的想法?如果没有,那么类型类型系统的哪些属性会导致这种情况呢?
  • 在我前面描述的三个通用Cloneable<T>接口中,哪个是最好的?最具潜在意义的最清晰、最简单、最简洁、最地道或最严格的意思。似乎第三种界面是唯一不允许冒牌货的界面。有没有更好的方法来做他们正在做的事情?
  • 在类型记录中是否有模式interface S<T extends S<T>>的名称,或者在任何类型系统中是否有更一般的名称?
EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-03-05 20:42:55

TypeScript有类型,您可以使用名为this的类型来引用您所称的“与自身相同的类型”。

代码语言:javascript
复制
interface Cloneable {
    clone(): this;
}

Cloneable的子类型的值中,this类型将与该子类型相同:

代码语言:javascript
复制
interface Foo extends Cloneable {
    bar: string;
}
declare const foo: Foo;
const otherFoo = foo.clone();
otherFoo.bar.toUpperCase();

注意,当您尝试实际实现Cloneable时,编译器会拒绝任何试图返回与您已经拥有的this对象不同的内容。在您的class A中,会发生这样的情况:

代码语言:javascript
复制
clone() { // error! Type 'A' is not assignable to type 'this'.
    return new A(this.a.slice()); // returns a deep copy of itself
}

这实际上是一个很好的错误,因为您不能选择子类型的底部。有人可以来扩展A (或者提供一个A & SomethingElse类型的值),this也会随之缩小范围:

代码语言:javascript
复制
class B extends A {
    z: string = "oopsie";
}
declare const b: B;
b.clone().z.toUpperCase();

如果您的clone()方法在A中没有预见到可能的子类型,比如B,那么它就不会以一种类型安全的方式工作。总之,一般来说,谈论如何处理这个问题可能是一种离题,但如果您并不真正关心可能的子类型,只想让编译器接受您的实现,那么您将需要一个类型断言,并且在子类型面前要小心一些:

代码语言:javascript
复制
clone() {
    return new A(this.a.slice()) as this;
}

你说的另一个技巧是:

代码语言:javascript
复制
interface Cloneable<T extends Cloneable<T>> {
    clone(): T;
}

名为F-有界多态性,这里的"F“只是”函数“的意思。您正在绑定(在TS中我们称之为约束),类型T由另一个类型组成,它是它的一个函数,F<T>。它也被称为“递归边界”

请注意,多态this是一种F界多态性的隐式类型,其中this可以被看作是某种“阴影”泛型类型参数。与使用显式类型参数T相比,您对T的控制更少,该参数使您能够选择子类型的底部位置:

代码语言:javascript
复制
class A implements Cloneable<A> {
/* ... */
    clone() {
        return new A(this.a.slice());
    }
}

class B extends A {
    z: string = "oopsie";
}
declare const b: B;
b.clone().z.toUpperCase() // error, no z

在这里,b.clone()生成A类型的值,而不是B类型,因为A implements Clonable<A>而不是Clonable<this>

但是这里的缺点是,您需要随身携带这个“额外”类型参数,无论您想要引用哪个Cloneable

代码语言:javascript
复制
const arrayOfCloneables: Cloneable<A>[] = [new A([1, 2, 3])];

操场链接到代码

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/66498870

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档