当前位置: 首页 > 知识库问答 >
问题:

收集。不可修改的XXX方法违反LSP?[闭门]

夹谷奇
2023-03-14

利斯科夫代换原理是固体力学的原理之一。现在,我已经多次阅读这一原则,并试图理解它。

这是我从中得出的结论,

这一原则与类层次结构中的强行为契约有关。子类型应该能够在不违反约定的情况下替换为超类型。

我也读了一些其他的文章,我有点迷失了思考这个问题。Collections.unmodifiableXXX()方法不违反LSP吗?

上面链接的文章摘录如下:

换句话说,当通过基类接口使用对象时,用户只知道基类的前置条件和后置条件。因此,派生对象不能期望这样的用户服从比基类要求的更强的先决条件

之前

class SomeClass{
      public List<Integer> list(){
           return new ArrayList<Integer>(); //this is dumb but works
      }
}

之后

class SomeClass{
     public List<Integer> list(){
           return Collections.unmodifiableList(new ArrayList<Integer>()); //change in implementation
     }
}

我不能改变某物类的实现,以在将来返回不可修改的列表。编译将工作,但是如果客户端以某种方式试图更改返回的List,那么它将在运行时失败。

这就是Guava为集合创建单独的ImmutableXXX接口的原因吗?

这不是直接违反了LSP吗?还是我完全弄错了?

共有3个答案

洪鸿博
2023-03-14

我不认为这是违反规定的,因为合同(即列表接口)规定变异操作是可选的。

韦繁
2023-03-14

是的,我相信你是对的。本质上,要实现LSP,您必须能够对子类型执行与超类型相同的任何操作。这也是LSP出现椭圆/圆问题的原因。如果椭圆有一个set偏心方法,而圆是椭圆的一个子类,并且对象应该是可变的,那么圆就无法实现set偏心方法。因此,你可以对椭圆做一些你不能对圆做的事情,因此违反了LSP。†类似地,对于常规的列表,您可以做一些事情,而对于由集合包装的列表则无法做到。不可修改列表,因此这是LSP冲突。

问题是这里有一些我们想要的东西(不可变的、不可修改的、只读的列表),但类型系统没有捕获到。在C#中,您可以使用IEnumerable,它捕获了一个序列的概念,您可以迭代并读取该序列,但不能写入该序列。但是在Java中只有List,它通常用于可变列表,但有时我们希望用于不可变列表。

现在,有人可能会说Circle可以实现set偏心率,并简单地抛出一个异常,类似地,当您试图修改一个不可修改的列表(或来自Guava的不可变列表)时,它也会抛出一个异常。但从LSP的角度来看,这并不意味着它是一个列表。首先,它至少违反了最小惊喜原则。如果调用方在尝试向列表中添加项目时遇到意外异常,这是非常令人惊讶的。如果调用代码需要采取步骤来区分它可以修改的列表和不能修改的列表(或者可以设置偏心率的形状,不能设置偏心率的形状),那么一个不能真正替代另一个。

如果Java类型系统有一个只允许迭代的序列或集合的类型,另一个允许修改,那就更好了。也许Iterable可以用于此目的,但我怀疑它缺少人们真正想要的一些功能(如size())。不幸的是,我认为这是当前Java collections API的一个限制。

一些人注意到,集合的文档允许实现从add方法抛出异常。我想这确实意味着,当涉及到add合同时,不能修改的列表遵守了法律条文,但我认为应该检查自己的代码,看看有多少地方可以保护对列表的变异方法的调用(addaddAllremoveclear)在声明没有违反LSP之前使用try/catch块。也许不是,但这意味着调用列表的所有代码都是。在收到的列表中添加,因为参数已损坏。

那肯定会说明很多问题。

(类似的参数可以表明,null是每种类型的成员的想法也违反了Liskov替换原则。)

我知道还有其他方法可以解决椭圆/圆问题,比如使它们不可变,或者删除setFrecentricity方法。我在这里只谈论最常见的情况,作为类比。

章盛
2023-03-14

LSP说,每个子类都必须遵守与超类相同的契约。集合的情况是否如此。因此,不可修改的xxx()取决于此合同的阅读方式。

Collections.unmodifiableXXX()返回的对象如果试图调用任何修改方法,则会抛出异常。例如,如果调用add(),则会抛出一个Unsupport tedoperationExc0019

add()的总合同是什么?根据API文件,它是:

确保此集合包含指定的元素(可选操作)。如果此集合因调用而更改,则返回true。(如果此集合不允许重复并且已包含指定元素,则返回false。)

如果这是完整的契约,那么实际上不可修改的变体不能在所有可以使用集合的地方使用。但是,本规范继续规定:

如果集合拒绝添加特定元素,除了它已经包含该元素之外,它必须抛出异常(而不是返回false)。这将保留一个不变量,即在该调用返回后,集合始终包含指定的元素。

这显式地允许实现具有不向集合添加add参数但导致异常的代码。当然,这包括收集客户的义务,他们考虑到这种(法律)可能性。

因此,行为子类型(或LSP)仍然实现。但是这表明,如果一个人计划在子类中有不同的行为,那也必须在顶层类的规范中预见到。

顺便说一句,问得好。

 类似资料:
  • 问题内容: Liskov替换原理是SOLID的原理之一。我已经读过几次这个原理,并试图理解它。 这就是我的所作所为, 此原则与类层次结构之间的强行为契约有关。子类型应该能够被超类型替换而不会违反合同。 我也读过其他文章,对这个问题我有些失落。难道方法不违反LSP? 上面链接的文章摘录: 换句话说,当通过对象的基类接口使用对象时,用户仅知道基类的前提条件和后置条件。因此, 派生对象不能期望此类用户遵

  • 来自Liskov替代原理-www.blackwasp。co.uk 不符合LSP的一个常见指示是当客户端类检查其依赖项的类型时。这可以通过读取人为描述其类型的对象的属性或通过使用反射来获得类型。通常,根据依赖项的类型,将使用开关语句执行不同的操作。这种额外的复杂性也违反了打开/关闭原则(OCP),因为随着更多子类的引入,客户端类需要修改。 以下技术(使用反射)是否会导致违反LSP? 依赖注入 注:我

  • 我问这个问题更多的是出于好奇,而不是真正关心它,但我一直想知道JavaScript事件系统是否违反了Liskov替换原则(LSP)。 通过调用,我们可以调度任意类型的,该事件可能由注册的处理。 如果我正确理解了LSP,那就意味着不应失败。但是,通常情况并非如此,因为事件侦听器通常会使用专门的子类型的属性。 在不支持泛型的类型化语言中,这种设计基本上需要将对象向下转换为中的预期子类型。 根据我的理解

  • 问题内容: 我在下面有此代码,并且通过执行以下行来获取ConcurrentModificationException: 代码: 堆栈是: 恰好在foreach行上: 我不明白为什么会发生此错误,因为我没有修改列表。 问题答案: 要知道,是 不是抄袭 收集数据,但只有包装原始集合在一个特殊的包装。因此,如果您修改原始集合,则会出现此错误。 如果要创建真正独立的不可修改的集合实例:

  • 问题内容: 我的情况与Code Complete中Steve McConnell 提到的情况非常相似。我唯一的问题是基于车辆,而三轮车恰好是根据法律,属于汽车。到目前为止,汽车只有四个轮子。无论如何,我的域都不必要地复杂,因此很容易遵循下面的cats示例。 对重写例程并且在派生例程中不执行任何操作的类要保持怀疑。这通常表明基类的设计存在错误。例如,假设您有一个Cat类和一个例程Scratch(),

  • 若我将public方法添加到子类中,并且客户端程序调用added方法,则客户端程序不能使用父对象而不是子类。 这个案子违反了LSP?