【问题标题】:Does this Typescript example violate the Liskov Subtitution Principle?这个 Typescript 示例是否违反了 Liskov 替换原则?
【发布时间】:2020-02-03 16:41:08
【问题描述】:

我有以下代码:

type T = { foo: string }
var t: T = { foo: 'foo' }

interface S { foo: string }
var s: S = t

所以我们知道T < S

这个怎么样?

t = s

好的,S < T 也是如此。

我们可以暗示S == T

现在介绍U

type U = { [key: string]: string }
var u: U = t

所以T < U。到目前为止一切顺利。

等一下!

u = s // Error!

这似乎违反了 Liskov 替换原则 (LSP):

如果 S 是 T 的子类型,则 T 类型的对象可以替换为 S 类型的对象

这是否违反了 LSP?有没有有关系吗?

抛开原则,这看起来相当愚蠢:

u = s    // Error!
u = <T>s // Ok!

这会被认为是一个错误吗?编译器肯定可以自己完成,不是吗?

【问题讨论】:

    标签: typescript liskov-substitution-principle


    【解决方案1】:

    TypeScript 的类型系统有些地方不完善;你已经找到this issue,其中类型别名而不是接口被赋予implicit index signatures。为类型提供隐式索引签名很有用,但通常不安全。考虑:

    const fooBar = { foo: "foo", bar: 123 };
    const tFooBar: T = fooBar; // okay
    const uFooBar: U = tFooBar; // okay?
    const whoopsie = uFooBar.bar; // string at compile time, number at runtime?!
    console.log(whoopsie);
    

    fooBar 是有效的T,因为它具有foo 类型的string 属性。所以你可以把它分配给tFooBar。然后由于 TypeScript 允许您将 T 类型的值分配给 U 类型的变量,因此您可以将 tFooBar 分配给 uFooBar。现在,如果您阅读uFooBarbar 属性,就会暴露出不健全性。根据U,它应该是string,但它是number。哎呀。

    隐式索引签名很有用,因为函数通常需要具有索引签名的值,并且它有助于已知属性符合索引签名的值被接受。所以,我们有这个有用的东西可以导致类型不安全的行为。应该怎么做?

    显然,TypeScript 的当前规则是:

    • 对象字面量/匿名类型被赋予隐式索引签名
    • 类型别名被赋予隐式索引签名
    • 接口没有被赋予隐式索引签名

    根据this comment by @RyanCavanaugh,显然这是故意的,而不是错误:

    只是为了填补人们的空白,此行为目前是设计使然。因为接口可以通过额外的声明来扩充,但类型别名不能,所以推断类型别名的隐式索引签名比接口的隐式索引签名“更安全”(在那个上加上重引号)。但如果这似乎有意义的话,我们也会考虑为接口做这件事。

    因此认为declaration merging 可能会破坏接口到索引签名的兼容性,但类型别名不能。也许他们愿意改变它,如果你有一个引人注目的用例,你可能想去 Github 问题并提及它。

    好的,希望对您有所帮助;祝你好运!

    Link to code

    【讨论】:

      猜你喜欢
      • 2015-01-01
      • 2017-07-04
      • 2021-11-17
      • 2012-01-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-09
      相关资源
      最近更新 更多