pla*_*alx 15 javascript liskov-substitution-principle javascript-events
我更多地是出于好奇而不是真正关心它,但我一直想知道JavaScript事件系统是否违反了Liskov替换原则(LSP).
通过调用EventTarget.dispatchEvent,我们可以调度一个Event可能由注册处理的任意类型EventListener.
interface EventListener {
void handleEvent(in Event evt);
}
Run Code Online (Sandbox Code Playgroud)
如果我正确理解LSP,那就意味着不anyEventListener.handleEvent(anyEvent)应该失败.但是,通常情况并非如此,因为事件侦听器通常会使用专用Event子类型的属性.
在不支持泛型的类型语言中,该设计基本上需要将Event对象向下转换为期望的子类型EventListener.
根据我的理解,上述设计可能被视为违反LSP.我是否正确或type通过注册监听器提供的简单事实EventTarget.addEventListener可以防止LSP违规?
编辑:
虽然每个人似乎都在关注Event子类没有违反LSP的事实,但实际上我关注的是EventListener实现者会通过加强EventListener接口的前提条件来违反LSP .void handleEvent(in Event evt)合同中没有任何内容告诉您通过传递错误的Event子类型可能会破坏某些内容.
在具有泛型的强类型语言中,接口可以表达为EventListener<T extends Event>使得实现者可以使合同明确,例如SomeHandler implements EventListener<SomeEvent>.
在JS中,显然没有实际的接口,但事件处理程序仍然需要符合规范,并且该规范中没有任何内容允许处理程序判断它是否可以处理特定类型的事件.
这不是一个真正的问题,因为不希望监听器自己被调用,而是EventTarget被注册并与特定类型相关联的监听器调用.
我只是对根据理论是否违反LSP感兴趣.我想知道是否要避免违规(如果理论上认为是这样),合同将需要像以下那样(尽管它在实用主义方面可能做得更糟糕):
interface EventListener {
bool handleEvent(in Event evt); //returns wheter or not the event could be handled
}
Run Code Online (Sandbox Code Playgroud)
LSP的含义非常简单:子类型不得以违反其超类型行为的方式运行."超类型"行为基于设计定义,但总的来说,它只是意味着可以继续使用该对象,就好像它是项目中任何位置的超类型一样.
因此,在您的情况下,它应遵守以下内容:
(1)A KeyboardEvent可用于Event预期的代码的任何地方;
(2)对于任何功能Event.func()中Event,相应的KeyboardEvent.func()接受的类型Event.func()的参数,或它们的超类型,则返回的类型Event.Func()或其亚型,和只抛出什么Event.func()抛出或它们的亚型;
(3)Event部分(数据成员)没有通过(历史规则)不能发生的方式KeyboardEvent进行调用.KeyboardEvent.func()Event.func()
什么是不通过LSP要求,是关于任何限制KeyboardEvent的实现func(),只要它在概念上,什么Event.func()应该.因此,它可以使用未使用的函数和对象Event,在您的情况下,包括Event超类型无法识别的自身对象的函数和对象.
编辑问题:
替换原则要求子类型(在概念上)与其超类型在预期超类型的任何地方的行为相同.因此,你的问题归结为"如果函数签名需要Event,那不是它所期望的那样吗?"
答案可能会让你感到惊讶,但它是 - "不,它没有".
原因是函数的隐式接口(或隐式契约,如果您愿意).正如您正确指出的那样,有些语言具有非常强大和复杂的类型规则,可以更好地定义显式接口,从而缩小允许使用的实际类型.然而,单独的正式论证类型并不总是完整的预期合约.
在没有强大(或任何)键入的语言中,函数的签名对于预期的参数类型没有任何或很少的说法.但是,他们仍然期望这些论点仅限于一些隐性契约.例如,这就是python函数的功能,C++模板函数的功能以及C语言中的功能void*.他们没有表达这些要求的句法机制这一事实并没有改变他们期望参数服从已知合同的事实.
即使非常强类型的语言(如Java或C#)也不能始终使用其声明的类型定义参数的所有要求.因此,例如,您可以调用multiply(a, b)和divide(a, b)使用相同的类型 - 整数,双精度,等等; 但是,devide()期望一个不同的合同:b不能是0!
当你Event现在看一下这个机制时,你可以理解并不是每个机制都可以Listener处理任何机制Event.一般Event和Listener论证的使用是由于语言限制(所以在Java中你可以更好地定义正式契约,在Python中 - 根本不是,在JS中 - 介于两者之间).你应该问自己的是:
代码中是否有一个位置,其中可能使用类型的对象Event(不是其他特定子类型Event,但Event本身),但KeyboardEvent可能不是?而另一方面 - 在代码中是否有一个位置Listener可以使用对象(而不是它的某个特定子类型),但是特定的侦听器可能不会?如果两者都没有答案 - 我们很好.
| 归档时间: |
|
| 查看次数: |
295 次 |
| 最近记录: |