我问这个问题更多的是出于好奇,而不是真正关心它,但我一直想知道JavaScript事件系统是否违反了Liskov替换原则(LSP)。
通过调用EventTarget。dispatchEvent
,我们可以调度任意类型的事件
,该事件可能由注册的EventListener
处理。
interface EventListener {
void handleEvent(in Event evt);
}
如果我正确理解了LSP,那就意味着anyEventListener。handleEvent(anyEvent)
不应失败。但是,通常情况并非如此,因为事件侦听器通常会使用专门的事件
子类型的属性。
在不支持泛型的类型化语言中,这种设计基本上需要将事件
对象向下转换为事件监听器
中的预期子类型。
根据我的理解,上述设计可能被视为违反LSP。在通过EventTarget注册侦听器时,我是正确的还是必须提供
类型的简单事实。addEventListener
是否防止LSP冲突?
编辑:
虽然每个人似乎都在关注
Event
子类没有违反LSP的事实,但实际上我担心EventListener
实现者会通过强化EventListener
接口的先决条件来违反LSP。void handleEvent(在evt事件中)
合同中没有任何内容告诉您,传递错误的事件
子类型可能会导致故障。
在具有泛型的强类型语言中,该接口可以表示为
EventListener
在JS中,显然没有实际的接口,但是事件处理程序仍然需要符合规范,并且该规范中没有任何内容允许处理程序判断它是否可以处理特定类型的事件。
这实际上不是一个问题,因为监听器不需要单独调用,而是由注册它并与特定类型关联的
EventTarget
调用。
我只是想知道,根据这个理论,LSP是否被违反了。我想知道,为了避免违约(如果从理论上考虑是这样的话),合同是否需要如下内容(尽管从实用主义的角度来看,它可能做得弊大于利):
interface EventListener {
bool handleEvent(in Event evt); //returns wheter or not the event could be handled
}
在这种情况下,JavaScript和浏览器事件API没有违反Liskov替换原则,但也没有努力实施它。
Java和C#等语言试图通过要求将值转换为给定类型或其子类型来防止程序员违反LSP,以便在需要该类型的上下文中使用它。例如,要传递需要矩形的
正方形
,正方形
必须实现或扩展矩形
。这将大大有助于确保对象的行为符合矩形的预期行为。然而,程序员仍然有可能违反LSP——例如,通过让
setWidth()
同时更改高度,正方形
的行为方式可能与矩形的行为方式不同。
在一个更真实的例子中,C#中的数组实现了
IList
接口,但是调用了。在该接口上添加()
将引发异常。因此,在不改变程序的正确性的情况下,不能总是在期望IList
的地方提供数组。LSP被违反了。
由于JavaScript没有编译时类型,因此它可以不费吹灰之力地阻止开发人员以不打算使用的方式使用任何对象。但在现实中,即使是强类型语言中的事件系统也倾向于鼓励稍微违反LSP,因为如果提供了错误的事件参数类型,向下转换事件参数将失败。
我的回答已经谈到了JavaScript,尤其是事件系统,是如何对违反Liskov替换原则的行为眨眼的。然而,我认为你提出的解决方案实际上没有任何价值。如果
handleEvents
返回false
,那么调用handleEvents
的系统会有什么不同?
在这种情况下,事件系统通过让开发人员决定如果“错误”事件类型传递给给定事件该怎么办来正确操作。根据应用程序的体系结构和需求,开发人员可以决定引入防护语句,他们可以决定这些防护语句是应该抛出错误还是只是静默返回。
不,JavaScript事件系统不违反Liskov替换原则(LSP)。
简单地说,LSP施加了以下约束“程序中的对象应可替换为其子类型的html" target="_blank">实例,而不改变该程序的正确性”
在JavaScript事件系统的特定示例中,EventListener
接口有一个函数签名,该签名需要一个事件
类型。实际上,这将通过子类型调用,例如KeyboardEvent
。这些子类型遵循LSP,因此,如果您提供在事件
接口上运行的handleEvent
实现,如果它被传递一个键盘事件
实例,它也将工作(即程序将是正确的)。
然而,这完全是学术性的,因为在实践中,事件处理程序通常希望使用子类型上定义的属性或方法,例如KeyboardEvent。代码
。在“强类型(*)语言(如C#)中,您可以在handleEvent
函数中从Event
强制转换为KeyboardEvent
。因为LSP定义了用子类型替换超级类型时所需的行为,所以从超级类型转换到子类型超出了LSP定义的行为范围。
使用JavaScript,您不需要强制转换来使用KeyboardEvents
接口,但是基本的基本原理适用。
简言之,事件系统遵守LSP,但实际上您的handleEvent
实现将访问超类型方法,因此将超出LSP定义的范围。
*
我在这里使用“强类型”这个词的意思非常松散!
LSP的含义很简单:子类型不得以违反其超类型行为的方式行事。“超类型”行为基于设计定义,但总的来说,它只是意味着可以继续使用该对象,就好像它是项目中任何地方的超类型一样。
因此,在您的情况下,它应遵守以下规定:
(1) 键盘事件
可用于预期发生事件
的任何代码位置;
(2) 对于任何函数事件。func()
在事件中,对应的
键盘事件。func()
接受事件的类型。func()
的参数或其超类型返回事件的类型。Func()
或其子类型,并仅抛出事件。func()
抛出或其子类型;
(3) 调用
KeyboardEvent
不会更改KeyboardEvent
事件的部分(数据成员)。func()
以事件无法发生的方式。func()
(历史记录规则)。
LSP不需要的是任何关于
func()
的KeyboardEvents
实现的限制,只要它在概念上有Event.func()
应该有的限制。因此,它可以使用不被事件
使用的函数和对象,在您的情况下,包括那些不被事件
超类型识别的它自己的对象。
对经编辑的问题:
替换原则要求子类型的行为(概念上)与它的超类型在任何需要超类型的地方的行为相同。因此,您的问题归结为“如果函数签名需要
事件
,这不是它所期望的吗?”
这个问题的答案可能会让你大吃一惊,但答案是“不,它不会”。
原因是函数的隐式接口(或者隐式契约,如果您愿意的话)。正如您正确指出的,有些语言具有非常强大和复杂的类型规则,可以更好地定义显式接口,从而缩小允许使用的实际类型。然而,仅形式参数类型并不总是完全的预期契约。
在没有强(或任何)类型的语言中,函数的签名对预期的参数类型没有任何或很少说明。然而,他们仍然期望这些论点仅限于一些隐性合同。例如,这是python函数所做的,C模板函数所做的,以及在C中获取
space*
的函数所做的。它们没有语法机制来表达这些需求的事实并不能改变它们期望参数服从已知契约的事实。
即使是非常强类型的语言,如Java或C#,也不能总是使用其声明的类型定义参数的所有要求。因此,例如,您可以使用相同的类型调用
乘(a,b)
和除(a,b)
,如整数、双精度等;然而,devide()
期望一个不同的契约:b
不能是0!
当您现在查看
事件
机制时,您可以理解并非每个监听器
都被设计来处理任何事件
。使用一般的事件
和监听器
参数是由于语言限制(所以在Java你可以更好地定义正式合同,在Python中-一点也不,在JS中-介于两者之间)。你应该问自己的是:
代码中是否有一个地方可以使用类型为
事件
的对象(不是事件
的其他特定子类型,而是事件
本身),但键盘事件
可能不使用?另一方面,代码中是否有一个地方可以使用Listener
对象(而不是它的特定子类型),但特定的监听器可能不会?如果两者的答案都是否定的-我们很好。
来自Liskov替代原理-www.blackwasp。co.uk 不符合LSP的一个常见指示是当客户端类检查其依赖项的类型时。这可以通过读取人为描述其类型的对象的属性或通过使用反射来获得类型。通常,根据依赖项的类型,将使用开关语句执行不同的操作。这种额外的复杂性也违反了打开/关闭原则(OCP),因为随着更多子类的引入,客户端类需要修改。 以下技术(使用反射)是否会导致违反LSP? 依赖注入 注:我
问题内容: Liskov替换原理是SOLID的原理之一。我已经读过几次这个原理,并试图理解它。 这就是我的所作所为, 此原则与类层次结构之间的强行为契约有关。子类型应该能够被超类型替换而不会违反合同。 我也读过其他文章,对这个问题我有些失落。难道方法不违反LSP? 上面链接的文章摘录: 换句话说,当通过对象的基类接口使用对象时,用户仅知道基类的前提条件和后置条件。因此, 派生对象不能期望此类用户遵
简介 Laravel 的事件提供了一个简单的观察者实现,能够订阅和监听应用中发生的各种事件。事件类保存在 app/Events 目录中,而这些事件的的监听器则被保存在 app/Listeners 目录下。这些目录只有当你使用 Artisan 命令来生成事件和监听器时才会被自动创建。 事件机制是一种很好的应用解耦方式,因为一个事件可以拥有多个互不依赖的监听器。例如,如果你希望每次订单发货时向用户发送
事件机制是一种很好的应用解耦方式。CatLib事件系统让我们可以订阅和监听程序中出现的各种事件。 应用程序已经默认提供了事件系统,供给全局事件使用。如果您要定义私有范围的事件可以这么做: var dispatcher = new EventDispatcher(); 名词定义 载荷是指程序调用所附带的上下文信息。不同的调用者所提供的上下文信息各不相同。 注册普通监听器 通过 AddListene
根据方法<code>java.util.concurrent的约定。未来#取消: 此方法返回后,对 isDone 的后续调用将始终返回 true。 Netty的Future接口扩展了它: 所以Netty应该遵守合同。但事实上Netty没有。您可以运行以下示例代码: 控制台应打印: 真 但实际上它打印: 假 以下方法也违反了合同: 我已经在github上创建了一个问题:问题 但是我仍然想在stack
考虑以下程序: (编译器资源管理器) GCC和Clang的各种版本都可以接受它,但MSVC不能接受它,因为MSVC编译失败,出现错误消息 第一条错误消息向我暗示了ODR违规--但如果这个程序是格式不良的NDR,我需要帮助理解为什么会这样。我已经检查了标准草案中的temp.over.link,但我不相信我对它的解释是正确的。根据我的理解,程序是可以的,因为这些函数模板有不同的签名。 在不太可能的情况