问题标签 [liskov-substitution-principle]

For questions regarding programming in ECMAScript (JavaScript/JS) and its various dialects/implementations (excluding ActionScript). Note JavaScript is NOT the same as Java! Please include all relevant tags on your question; e.g., [node.js], [jquery], [json], [reactjs], [angular], [ember.js], [vue.js], [typescript], [svelte], etc.

0 投票
1 回答
205 浏览

aggregate - 扩展基类的聚合类 - 违反 LSP?

维基百科上的里氏替换原则(LSP)


假设我有一个Alien带有numFingers属性*的类。有时,我需要numFingers从数据库中提取按其他字段值分组的总和。在这些情况下,我不需要单独操作每条记录,但我确实需要访问它们的许多功能——能够获取属性、对它们执行一些基本逻辑等。这可能包括从数千个记录中汇总的数据记录,因此Alien当数据库查询可以为我完成求和工作时,实例化数千个对象几乎没有意义。

我想创建一个名为 的扩展类AlienAggregate,其属性是从分组和求和查询中设置的。这个类将允许我调用任何Alien方法。这两个类的功能之间的唯一区别是GetID(). 聚合类没有 ID,因为它的数据来源于任意数量的记录。因此,调用GetID()onAlienAggregate会引发异常。

这是否违反了 Liskov 替换原则?有没有更好的方法来处理呼叫GetID()Alien有没有更好的方法来设计和类之间的关系AlienAggregate

*实际名称可能已更改,只是因为我可以。

0 投票
2 回答
1102 浏览

inheritance - 为什么将实例声明为超类型却将其实例化为子类型,加上里氏替换原则

几天来,我一直在尝试理解 Liskov 替换原则,在使用非常典型的 Rectangle/Square 示例进行一些代码测试时,我创建了下面的代码,并提出了 2 个关于它的问题。

问题 1:如果我们有超类/子类关系,为什么我们要将一个实例声明为超类型,但将其实例化(新建)为子类型?

我理解为什么,如果我们通过接口进行多态性,我们希望以这种方式声明和实例化变量:

但是,现在我在旧的编程类和一些博客示例中回忆起它,当通过继承使用多态性时,我仍然会看到一些示例,其中一些代码会以这种方式声明变量

在我下面的代码中,Square 继承自 Rectangle,所以当我以这种方式创建一个新的 Square 实例时:

它仍然可以被视为一个矩形,或者添加到一个通用的矩形列表中,那么为什么有人仍然想将它声明为 Rectangle = new Square() 呢?是否有我没有看到的好处,或者需要这样做的场景?就像我说的,我下面的代码工作得很好。

}

尽管这应该违反 Liskov 替换原则,但我得到以下输出:

"宽度:90,高度:80

ConsoleApp.Program+矩形

宽度:80,高度:80

ConsoleApp.Program+Square

宽度:80,高度:80 ConsoleApp.Program+Square

问题 2:那么,此代码示例为何或如何破坏 LSP?仅仅是因为所有边相等的 Square 不变量打破了 Rectangle 不变量,边可以独立修改吗?如果是这个原因,那么 LSP 违规只是理论上的吗?或者,在代码中,我怎么能看到这个代码违反了原则?

编辑:在我正在阅读的一篇 LSP 博客文章中提出了第三个问题,但没有答案,所以就是这个

问题 3:开闭原则指出我们应该通过新的类(继承或接口)引入新的行为/功能。因此,例如,如果我在基类中有一个 WriteLog 方法,它没有先决条件,但是我引入了一个新的子类,它覆盖了该方法,但只有在事件非常关键时才实际写入日志....如果这是新的预期功能(对子类型进行强化的前提条件),这仍然会破坏 LSP 吗?在这种情况下,这两个原则似乎相互矛盾。

提前致谢。

0 投票
1 回答
2589 浏览

design-patterns - 适配器模式与 Liskov 替换

适配器设计模式用于将类的接口(目标)转换为客户期望的另一个接口(适配器)。Adapter 让不兼容的类可以一起工作,因为它们的接口不兼容。

适配器模式可以通过两种方式实现,继承(适配器模式的类版本)和组合(适配器模式的对象版本)。

我的问题是关于使用继承实现的适配器模式的类版本。

这是绘图编辑器的示例:

图1:

我们想复用TextView类来实现TextShape,但是接口不同,所以TextView和Shape对象不能互换使用。

是否应该更改 TextView 类以符合形状界面?也许不是。

TextShape 可以通过以下两种方式之一使 TextView 界面适应形状的界面:

  1. 通过继承Shape的接口和TextView的实现(Adapter模式的类版)
  2. 通过在 TextShape 对象内部组成一个 TextView 实例,并使用 TextView 实例(适配器模式的对象版本)实现 TextShape 的接口。

类适配器

图 2:

现在的问题:-)。TextShape 是否继承自 Shape 尤其是 TextView 是有效的“是”关系?如果不是,这是否违反了Liskov 的替代原则

0 投票
3 回答
324 浏览

oop - 关于LSP(Liskov Substitution Principle)和子类型的问题

LSP

如果 q(x) 是关于 T 类型的对象 x 的可证明属性,那么 q(y) 应该对 S 类型的对象 y 为真,其中 S 是 T 的子类型。

我可以改写如下:

q(x) 对 T 的任何 x 为真 => q(y) 对 T 的任何子类型的任何 y 为真

现在另一个声明呢?

q(x) 对 T 的任何 x 都为真,并且 q(y) 对 S 的任何 y 都为真 => S 是 T 的子类型

是否有意义 ?我们可以用它作为 的定义subtype

0 投票
3 回答
3437 浏览

constructor - 构造函数应该遵守里氏替换原则吗?

我通常会尝试确保我的对象实例符合Liskov Substitution Principle,但我一直想知道人们是否认为 LSP 也应该适用于构造函数?

我试过用谷歌搜索,但无论哪种方式,我都找不到任何强烈的意见。

我应该注意,我的大部分编码都是用 Ruby 编写的,但有时我会发现我的子类构造函数与父类略有不同。它们采用相同的基本参数集,通常还有额外的参数。有时,其他类方法也会发生这种情况。

在我的脑海里,这一直感觉像是违反了 LSP,但我想看看其他人是否也有这种感觉。

0 投票
3 回答
426 浏览

java - 我的讲师对 Liskov 替换原则的定义是不正确的,还是我误解了?

由于(Liskov)替换原则,以下内容确实有效,该原则表示,如果某个类的实例需要引用,那么您可以替换对该类的任何子类的实例的引用。

现在,据我了解,在这种情况下,我正在创建一个Cat对象(因此正在堆中创建内存空间),然后我将一个名为“felix”的对象引用变量分配给新创建的Cat对象。引用变量的类型是Cat,因此只能控制Cat及其任何子类Cat

然后我创建一个Objecttype 的引用变量Object,并将其指向 felix ( Cat) 对象,该对象可以工作但功能有限,因为 JVM 现在看到 felix 对象的 type Object,因此,例如,如果purr()Cat类,felix 将无法再使用它。

所以 typeCat应该有一个引用,但是我们为 cat 类型的超类(而不是上面定义中所说的子类)提供了一个引用,这是允许的,但功能有限(除非你进行强制转换)。

我是正确的还是错误的?

0 投票
7 回答
28599 浏览

c# - 为什么数组实现IList?

参见System.Array类的定义

理论上我应该能写到这点并且开心

我也应该能够从 iList 调用任何方法

我的问题不是为什么我得到一个异常,而是为什么 Array 实现 IList

0 投票
3 回答
97 浏览

stack - 堆栈、有界堆栈和 Liskov 替换属性

有界堆栈数据结构(具有上限的堆栈)能否在不违反 Liskov 替换属性的情况下实现为传统堆栈的子类型?

可以使用常规堆栈来代替有界堆栈,但是如果有界堆栈具有足够大的限制,则只能使用有界堆栈来代替常规堆栈。我对这个想法正确吗?

liskov 属性是否相反?

谢谢。

0 投票
4 回答
1192 浏览

java - 抽象属性是否违反了 Liskov 替换原则?

假设我有一个抽象类,例如:

我的程序会Pet根据是否设置特殊处理标志来区别对待 s。我的问题是这是否算作违反了 Liskov 替代原则,该原则指出:

[...] 在计算机程序中,如果 S 是 T 的子类型,那么 T 类型的对象可以被 S 类型的对象替换 [...]而不会改变该程序的任何期望属性(正确性,执行的任务), ETC。)。

0 投票
4 回答
658 浏览

java - 继承和 LSP

提前为一个冗长的问题道歉。反馈特别感谢这里。. .

在我的工作中,我们对日期范围做了很多事情(如果你愿意的话,日期期间)。我们需要进行各种测量,比较两个日期期间之间的重叠等。我设计了一个接口、一个基类和几个派生类,它们可以很好地满足我的需求:

  • 日期期间
  • 日期期间
  • 日历月
  • 日历周
  • 财政年度

DatePeriod 超类的基本要素如下(省略了所有令人着迷的特性,这些特性是我们需要这组类的基础......):

(Java 伪代码):

基类包含一组相当专门的方法和属性,用于操作日期周期类。派生类更改设置相关期间的起点和终点的方式。例如,对我来说,CalendarMonth 对象确实“是一个”DatePeriod 是有意义的。但是,出于显而易见的原因,日历月具有固定的持续时间,并且具有特定的开始日期和结束日期。事实上,虽然 CalendarMonth 类的构造函数与超类的构造函数相匹配(因为它具有 startDate 和 endDate 参数),但这实际上是简化构造函数的重载,它只需要一个 Calendar 对象。

对于 CalendarMonth,提供任何日期都将生成一个 CalendarMonth 实例,该实例从相关月份的第一天开始,到该月的最后一天结束。

为冗长的序言道歉。鉴于上述情况,这种类结构似乎违反了 Liskov 替换原则。虽然可以在任何可能使用更通用的 DatePeriod 类的情况下使用 CalendarMonth 的实例,但关键方法的输出行为会有所不同。换句话说,必须意识到在给定情况下正在使用 CalendarMonth 的实例。

虽然 CalendarMonth(或 CalendarWeek 等)遵守通过基类使用 IDatePeriod 建立的合同,但在使用 CalendarMonth 并且预期普通旧 DatePeriod 的行为的情况下,结果可能会变得非常扭曲。. . (请注意,在基类上定义的所有其他时髦方法都可以正常工作 - 只有开始和结束日期的设置在 CalendarMonth 实现中有所不同)。

有没有更好的方法来构建它,以便在不影响可用性和/或重复代码的情况下保持对 LSP 的正确遵守?