我出于好奇而不是真正关心来询问这个问题,但是我一直想知道JavaScript事件系统是否违反了Liskov substitution principle (LSP)

通过调用 EventTarget.dispatchEvent ,我们可以调度可能由已注册 Event 处理的任意类型的 EventListener

interface EventListener {
  void handleEvent(in Event evt);
}

如果我正确理解了LSP,那意味着anyEventListener.handleEvent(anyEvent)应该不会失败。但是,通常不是这样,因为事件侦听器将经常使用特殊的Event子类型的属性。

在不支持泛型的类型化语言中,该设计基本上需要将Event对象向下转换为EventListener中的预期子类型。

根据我的理解,以上设计可以视为违反了LSP。我是否正确,还是通过 type 注册侦听器时必须提供EventTarget.addEventListener的简单事实可以防止LSP冲突?

编辑:

尽管每个人似乎都在关注Event子类没有违反LSP这一事实,但我实际上担心EventListener实现者会通过加强EventListener接口(interface)的前提条件来违反LSP。 void handleEvent(in Event evt)合约中没有任何内容告诉您,传递错误的Event子类型可能会破坏某些内容。

在具有泛型的强类型语言中,该接口(interface)可以表示为EventListener<T extends Event>,以便实现者可以使契约(Contract)明确,例如SomeHandler implements EventListener<SomeEvent>

在JS中,显然没有实际的接口(interface),但是事件处理程序仍然需要符合规范,并且该规范中没有任何内容允许处理程序告诉它是否可以处理特定类型的事件。

这并不是真正的问题,因为不希望监听器被单独调用,而是由注册它的EventTarget和与特定类型相关联的调用。

我只是对根据理论是否违反LSP感兴趣。 我想知道是否要避免违反契约(Contract)(如果从理论上讲是这样的话),契约(Contract)将必须是以下内容(即使就实用主义而言,它的弊大于利):
interface EventListener {
  bool handleEvent(in Event evt); //returns wheter or not the event could be handled
}

最佳答案

LSP的含义非常简单:子类型不得以违反其父类(super class)型行为的方式运行。这种“父类(super class)型”行为是基于设计定义的,但是通常,这仅意味着人们可以继续使用该对象,就好像它是项目中任何地方的父类(super class)型一样。

因此,在您的情况下,它应遵守以下规定:

(1)可以在需要使用KeyboardEvent的代码的任何位置使用Event

(2)对于Event.func()中的任何函数Event,相应的KeyboardEvent.func()接受Event.func()的参数类型或它们的父类(super class)型,返回Event.Func()的类型或其子类型,并且仅抛出Event.func()所抛出的变量或其子类型;

(3)调用Event不会更改KeyboardEventKeyboardEvent.func()部分(数据成员),而Event.func()(历史规则)不会发生这种情况。

LSP要求的而不是是关于KeyboardEventfunc()实现的任何限制,只要从概念上讲应该是什么Event.func()即可。因此,它可以使用Event未使用的函数和对象,在您的情况下,包括自己的对象的Event父类(super class)型无法识别的函数和对象。

转到已编辑的问题:

替代原理要求子类型在概念上(无论在概念上)在父类型所期望的任何地方都以相同的方式起作用。
因此,您的问题归结为以下问题:“如果函数签名需要Event,那不是它所期望的吗?”

答案可能会让您感到惊讶,但这是-“不,它不会”。

这样做的原因是该函数的隐式接口(interface)(或隐式协定,如果您愿意)。正如您正确指出的那样,有些语言具有非常强大和完善的键入规则,可以更好地定义显式接口(interface),从而缩小了允许使用的所有实际类型。但是,仅形式论证类型并不总是完整的预期契约(Contract)。

在没有强类型(或任何类型)的语言中,函数的签名对预期的参数类型什么也没有说。但是,他们仍然希望将参数限制为某些隐式契约。例如,这就是python函数的作用,C++模板函数的作用以及在C语言中获得void*的函数的作用。他们没有表达这些要求的句法机制,这一事实并没有改变他们期望论点服从已知契约(Contract)的事实。

即使是非常强类型的语言(如Java或C#)也无法始终使用其声明的类型来定义参数的所有要求。因此,例如,您可以使用相同的类型来调用multiply(a, b)divide(a, b)-整数, double 数,等等;但是,devide()期望使用不同的契约(Contract):b不能为0!

现在,当您查看Event机制时,您可以了解到并非每个Listener都旨在处理任何Event。通用EventListener参数的使用是由于语言限制(因此,在Java中,您最好在Python中定义正式契约(Contract),而不是在JS中定义正式契约(Contract))。您应该问自己的是:

代码中是否有地方可以使用Event类型的对象(不是Event的其他特定子类型,而是Event本身的其他特定子类型),但是可能不使用KeyboardEvent?另一方面,在代码中是否可以使用Listener对象(而不是该对象的某些特定子类型),但是该特定侦听器可能没有?如果两者的答案都为否,那么我们很好。

关于javascript - JavaScript事件系统是否违反LSP?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/43244192/

10-10 16:23