Nar*_*hai 38 java oop collections liskov-substitution-principle
Liskov替换原则是SOLID的原则之一.我现在已经多次阅读过这个原则,并试图理解它.
这是我用它做的,
这一原则与阶级等级之间强烈的行为契约有关.子类型应该能够在不违反合同的情况下用超类型替换.
我也读过其他一些文章,我有点想不起这个问题.千万Collections.unmodifiableXXX()方法不违反LSP?
以上链接文章的摘录:
换句话说,当通过其基类接口使用对象时,用户只知道基类的前提条件和后置条件.因此,派生对象不能指望这样的用户遵守比基类所要求的更强的前提条件
之前
class SomeClass{
public List<Integer> list(){
return new ArrayList<Integer>(); //this is dumb but works
}
}
Run Code Online (Sandbox Code Playgroud)
后
class SomeClass{
public List<Integer> list(){
return Collections.unmodifiableList(new ArrayList<Integer>()); //change in implementation
}
}
Run Code Online (Sandbox Code Playgroud)
我SomeClass将来无法改变返回不可修改列表的实现.编译将工作,但如果客户端以某种方式尝试更改List返回,那么它将在运行时失败.
这就是为什么Guava 为集合创建了单独的ImmutableXXX接口?
这不是直接违反LSP或我完全弄错了吗?
Mat*_*att 37
LSP表示每个子类必须遵守与超类相同的合同.这是否属实,Collections.unmodifiableXXX()取决于该合同的内容.
Collections.unmodifiableXXX()如果尝试对它们调用任何修改方法,则返回的对象抛出异常.例如,如果add()被调用,UnsupportedOperationException将抛出一个.
什么是一般合同add()?根据API文档,它是:
确保此集合包含指定的元素(可选操作).如果此集合因调用而更改,则返回true.(如果此集合不允许重复并且已包含指定的元素,则返回false.)
如果这是完整的合同,那么确实不可修改的变体不能用于可以使用集合的所有地方.但是,规范仍在继续,并说:
如果一个集合因为已经包含该元素的原因而拒绝添加特定元素,那么它必须抛出异常(而不是返回false).这保留了在此调用返回后集合始终包含指定元素的不变量.
这明确允许实现具有不add向集合添加参数但导致异常的代码.当然,这包括他们将该(合法)可能性考虑在内的集合客户的义务.
因此,仍然实现行为子类型(或LSP).但这表明,如果计划在子类中有不同的行为,那么在顶级类的规范中也必须预见到这些行为.
顺便问一下好问题.
Dav*_*rad 12
是的,我相信你说得对.从本质上讲,要实现LSP,您必须能够使用超类型执行的子类型.这也是椭圆/圆问题出现在LSP上的原因.如果Ellipse有一个setEccentricity方法,而Circle是Ellipse的子类,并且这些对象应该是可变的,那么Circle就无法实现该setEccentricity方法.因此,你可以用Ellipse做一些你不能用Circle做的事情,因此违反了LSP.†同样,你可以用常规做一些你List不能用一个包裹的东西Collections.unmodifiableList,所以这是一个LSP违规.
问题是这里有一些我们想要的东西(一个不可变的,不可修改的,只读的列表),它不是由类型系统捕获的.在C#中你可以使用IEnumerable它来捕获你可以迭代和读取的序列的想法,但不能写入.但是在Java中只存在List,它通常用于可变列表,但我们有时会将其用于不可变列表.
现在,有些人可能会说Circle可以实现setEccentricity并简单地抛出异常,类似地,当您尝试修改它时,不可修改的列表(或来自Guava的不可变列表)会抛出异常.但这并不意味着从LSP的角度来看它是一个 List.首先,它至少违反了最不惊讶的原则.如果调用者在尝试将项添加到列表时遇到意外异常,则非常令人惊讶.如果调用代码需要采取措施来区分它可以修改的列表和它不能修改的列表(或者它可以设置的偏心形状,以及它不能修改的形状),那么一个不能真正替代另一个.
如果Java类型系统具有仅允许迭代的序列或集合的类型,并且允许修改的另一个类型,则会更好.也许Iterable可以用于此,但我怀疑它缺少一些size()人真正想要的功能.不幸的是,我认为这是当前Java集合API的限制.
有些人注意到文档Collection允许实现从add方法中抛出异常.我认为这确实意味着无法修改的列表在遵守合同时遵守法律条款,add但我认为应该检查一个人的代码并查看有多少地方可以保护对变更方法的调用.表(add,addAll,remove,clear)与争辩说,LSP不被破坏之前try/catch块.也许它不是,但这意味着所有调用List.add它作为参数接收的List的代码都会被破坏.
那肯定会说很多.
(类似的论点可以表明,作为null每种类型成员的想法也违反了Liskov替代原则.)
†我知道还有其他方法可以解决Ellipse/Circle问题,例如使它们不可变,或者删除setEccentricity方法.我在这里只是谈论最常见的情况,作为类比.
我不相信它是违规的,因为合同(即List接口)说变异操作是可选的.