在打字稿中创建通用的“可克隆”界面

2024-02-04

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

// 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 whose .clone()方法返回某个其他类的可克隆对象,该对象无法分配给类型Faker。为了解决这个问题,我们可以按类型参数化 Cloneable 接口:

// 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;
}

然后将类型参数限制为可克隆,我们可以指定T extends Cloneable<T>:

  // Interface that requires a cloneable
interface RequiresCloneable<T extends Cloneable<T>> {
  //...
}

所有三个参数化接口都承认类A。然而,我们失去了在不知道其实现类型的情况下指定特定值应该是 Cloneable 的能力:

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 接口或类型来捕获对象的克隆应该与其自身类型相同的想法?如果不是,那么 Typescript 类型系统的哪些属性使得这样呢?
  • 三个通用的中Cloneable<T>我上面描述的接口,哪一个是最好的?最佳可能意味着最清晰、最简单、最简洁、最惯用或最严格。似乎第三个界面是唯一不允许造假者的界面。有没有更好的方法来做他们正在做的事情?
  • 图案有名字吗interface S<T extends S<T>>在打字稿中,或更普遍地在任何类型系统中?

TypeScript 有多态性this types https://www.typescriptlang.org/docs/handbook/advanced-types.html#polymorphic-this-types,您使用的地方type named this指您所说的“与自身相同的类型”。

interface Cloneable {
    clone(): this;
}

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

interface Foo extends Cloneable {
    bar: string;
}
declare const foo: Foo;
const otherFoo = foo.clone();
otherFoo.bar.toUpperCase();

但请注意,当您尝试实际实施 Cloneable,编译器将阻止任何试图返回除相同内容之外的内容的内容this你已经拥有的对象。在你的class A, 有时候是这样的:

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), and this将随之缩小:

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

If your clone()中的方法A没有预见到可能的子类型,例如B,那么它将无法以类型安全的方式工作。无论如何,谈论一般如何处理这个问题可能是一个题外话,但是如果您并不真正关心可能的子类型并且只是希望编译器接受您的实现,那么您将需要一个类型断言 https://www.typescriptlang.org/docs/handbook/basic-types.html#type-assertions,以及面对子类型时的一些注意事项:

clone() {
    return new A(this.a.slice()) as this;
}

您正在谈论的另一种技术:

interface Cloneable<T extends Cloneable<T>> {
    clone(): T;
}

叫做F-界多态性 https://en.wikipedia.org/wiki/Bounded_quantification#F-bounded_quantification,其中“F”在本例中仅表示“函数”。你正在跳跃(在 TS 中我们称之为约束 https://www.typescriptlang.org/docs/handbook/generics.html#generic-constraints) 方式T通过作为它的函数的另一种类型,F<T>。它也被称为“递归边界” https://stackoverflow.com/questions/7385949/what-does-recursive-type-bound-in-generics-mean.

注意多态性this是 F 有界多态性的隐式排序,其中this可以看作是一种“影子”泛型类型参数。你的控制权较少this与使用显式类型参数相比T,这使您能够选择子类型触底的位置:

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

Here, b.clone()产生一个类型的值A并不是B, 因为A implements Clonable<A>并不是Clonable<this>.

但这里的缺点是你需要在你想要引用的任何地方携带这个“额外”类型参数Cloneable:

const arrayOfCloneables: Cloneable<A>[] = [new A([1, 2, 3])];

Playground 代码链接 https://www.typescriptlang.org/play?ts=4.2.2#code/HYQwtgpgzgDiDGEAEAFA9gGwJ5jQJxgAsBLeAFRKiQG8BYAKCSaWOABcI8AzBZAYQxpgEEACMMyOo2Yz4g4QAoAlAC4kbSgG4GMgL4MdzVh268kAMTRokEAB4dgAEyoChI8ZMMymokHjVQbHisAOba0kz6EUiOEHJ+yPBCgUhcVmqWaOGyyWxIaBqcmUgAvKlWAHRybsrZzAWERZW+eBVsaACqMDCcfCBQELUG0fFQVACCLGAwEpDsLvLuEjRe3iBqwACuYKKcANoAunXeSEnAgXib8O14CutIWzv7B0or0SdMGsRQFSClSCBjt4oh9TotlG9QTI8BA2Js8MAHhAAO5IcYKL4-EAVKAYUiDJSvfrqLSrPSrKKrUZUABCNnsECcE0hHwAXgEgqF-gAiKwwKDECDcoEg5ixeIw065JCiNQ0oGiKrgpQVVltTrdXr9AnDGTGTg8RBIABKEAAjptiDCoK5hGIJAAeMj0hzOJC2pYQAB8LJOAHo-RUgxTdcwzik-HgQFgAPJcD326BqBMeQ7-PbCVHovYARgANEgAEwFgDMLyODEp9FAkFgZnMNLQmycEEcvpY7ANZhTjuddldCzciadXp9UlB1UUqiQZBFoaY+tMRuK-cZbp7EAdmTHZOYLQ5wWAYRD0XFGASUvOeTSaAyViB4byDSa1jKN6VNSUQOfeEyFRa6pdD0eB9AMQz0FS55jGiUwzBAcxsIOdoeA64w7u8zD3I8ux4IcQI5FelzXPgdwbNsOGHK845Qp8lC-P8gK7pETFgp+7agjCcIIkiWYYnR2K4viyhfixorMSMUG0i6a7MtRJzskgFxcmUvJoPygrCieMhnhej4ynKCoflOqqAZqIHahCAY2HgeD4AWwDWKy84diYhrIKaFpWtAG5OtJTLuosw5kKO7HMAGQYVCeVLSpG0ZxhuUDJoFKFoWmZQZiiaIKLmBbFkgZZKBWEHRJWQA

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

在打字稿中创建通用的“可克隆”界面 的相关文章

随机推荐

  • MySQL:主机列中的 % 代表什么以及如何更改用户密码

    嗯 这就是我能看到的 select host user from mysql user host user me 127 0 0 1 root 1 root localhost localhost debian sys maint loca
  • JDBC 字符编码

    我有一个在 GlassFish 3 上运行的 Java Web 应用程序和在 MySQL 上运行的 JPA EclipseLink 我面临的问题是 如果我使用以下命令将实体保存到数据库中update 方法 String领域失去完整性 显示而
  • 在编译时计算小整数的阶乘

    我刚刚实现 再次 一个递归模板 用于在编译时计算整数的阶乘 谁会想到有一天我实际上会需要它 不过 我没有自己动手 而是去了Boost http www boost org 寻找答案 然而 特殊数学中的阶乘函数明确禁止将其与整数类型一起使用
  • 在Java BufferedImage中绘制完全透明的“白色”

    这可能听起来有点奇怪 但请耐心听一下 那里is一个原因 我正在尝试在灰色背景上的文本周围生成白色发光 为了生成发光 我创建了一个比文本大的新 BufferedImage 然后将白色文本绘制到图像的画布上 并通过卷积运算 http docs
  • 如何在Python中将csv转换为json?

    我对编程非常陌生 过去 3 4 周一直在学习 python 这是给出的作业之一 Input A B C D 1 2 3 4 5 6 7 8 Output A 1 B 2 C 3 D 4 A 5 B 6 C 7 D 8 我一直在尝试将代码设置
  • stat_smooth 和 geom_ribbon 之间的交互不良

    我正在回答这个问题 https stackoverflow com questions 64574595 geom density returns plot without considering real values 这需要绘制平滑区域
  • 如何在Python中基于if语句保存一个文档?

    我正在尝试根据 if 语句保存文档 我在这里创建单选按钮 info Option 1 Option 2 Option 3 vars for idx i in enumerate info var IntVar value 0 vars ap
  • NoClassDefFoundError:配置属性源

    从昨天开始 在全新安装后 我们的 spring boot 项目在没有更改 Maven 文件 库或其他配置的情况下出现了问题 我们确实尝试将 SpringFrameWork 和 Spring boot 的 pom 版本更新到 1 5 4 Re
  • 整数数组作为字典的键

    我希望拥有使用整数数组作为键的字典 如果整数数组具有相同的值 甚至不同的对象实例 它们将被视为相同的键 我该怎么做呢 以下代码不起作用b是不同的对象实例 int a new int 1 2 3 int b new int 1 2 3 Dic
  • 如何获取所有 Spark 配置以及默认配置?

    我正在开发一个项目 需要收集所有 Spark 配置 问题是 如果没有明确设置参数 我将需要默认值 有没有办法获取所有配置 包括所有默认值 我尝试过 sc getConf getAll 但通过这种方式 我没有得到默认值 SparkListen
  • Java 卡连接到模拟器失败

    我正在尝试测试 Java Card 小程序以建立与 cref 等模拟器的连接 try sckClient new Socket localhost 9025 InputStream is sckClient getInputStream O
  • 如何使用 PHP、CURL 抓取 javascript 网站 [重复]

    这个问题在这里已经有答案了 可能的重复 如何在 PHP 应用程序内从另一个站点呈现 javascript https stackoverflow com questions 5332161 how do i render javascrip
  • flutter:地理定位器不起作用,请确保清单中至少定义了 ACCESS_FINE_LOCATION 或 ACCESS_COARSE_LOCATION

    我正在尝试使用地理定位器 并且我添加了两者
  • Java中的中断线程

    我想优雅地关闭线程 我在网上看到了很多代码并在这里查询 我认为有两种关闭方法 使用布尔标志 只要改变一个标志 我们就可以在 run 方法中破坏代码 使用中断方法 我的问题是 为什么要避免使用布尔标志来正常关闭线程 当我运行示例程序时 它运行
  • 链接静态方法

    这可能没有解决方案 或者我可能找不到解决方案 但这里是 注意 我知道下面的代码是不正确的 我只是想展示我到底是什么想去完成 我想做一些事情 public class ActionBarHandler public static Action
  • Rails - 如何获取访问者的IP地址?

    我需要将访问者的 IP 地址存储到我们的数据库中 这是我尝试执行此操作的方法 ip request remote ip ip request env REMOTE ADDR 但在这两种情况下 ip变量存储值127 0 0 1 即使我将应用程
  • C:对不完整类型的数组添加下标合法吗?

    我在标准中找不到相关位 但 gcc 和 clang 允许这样做 所以我想我想知道它是编译器扩展还是语言的一部分 如果可以的话请提供一个链接 这可能会因以下情况而出现 extern char arr func arr 7 No error 后
  • 作为 IntelliJ 插件的“查找用法”功能

    我试图在 IntelliJ IDEA 中找到一种方法来查找特定项目中一些库方法调用和类的所有用法 目标是编译引用这些特定方法或类的类列表 我该怎么办呢 我可以看到有一个MethodReferencesSearch看起来它可能会有所帮助 但是
  • effective-pom是超级pom和应用程序POM之间的合并

    我尝试使用 mvn help effective pom 命令在示例应用程序上生成有效的 pom http books sonatype com mvnref book reference pom relationships sect po
  • 在打字稿中创建通用的“可克隆”界面

    考虑一个打字稿接口 它描述了对象具有的约束 clone 返回相同类型的对象 或者可能是可分配给其自己类型的对象 的方法 我想象的一个例子 Naive definition interface Cloneable clone gt Clona