此打字稿示例是否违反了Liskov替代原则?

时间:2019-10-05 19:34:56

标签: typescript liskov-substitution-principle

我有以下代码:

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!

这会被视为错误吗?编译器肯定可以单独执行此操作吗?

1 个答案:

答案 0 :(得分: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的{​​{1}}属性。因此,您可以将其分配给string。然后,由于TypeScript允许您将类型tFooBar的值分配给类型T的变量,因此可以将U分配给tFooBar。现在,如果您阅读了uFooBar的{​​{1}}属性,就会暴露出声音的不健全。根据{{​​1}},它应该是bar,但是它是uFooBar。糟糕!

隐式索引签名很有用,因为函数通常需要带有索引签名的值,并且对于那些已知属性符合索引签名的值很有用。因此,我们有一个有用的东西,它可能导致类型不安全的行为。应该怎么办?

显然,TypeScript的当前规则是:

  • 对象文字/匿名类型被赋予隐式索引签名
  • 类型别名赋予隐式索引签名
  • 未为接口提供隐式索引签名

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

  

仅是为了填补人员缺席,这种行为目前是设计使然。由于可以通过其他声明来增强接口,但不能使用类型别名来进行扩展,因此推断类型别名的隐式索引签名比接口的隐式索引签名“更安全”(在该引用上使用大量引号)。但是,如果看起来合理,我们也将考虑对接口执行此操作。

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

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

Link to code