我有这段代码:
abstract class Entity
{
// blah-blah-blah
}
abstract class BaseCollection
{
public void add(Entity entity);
}
我从Entity和BaseCollection类派生出来:
class User extends Entity
{
}
class UserCollection extends BaseCollection
{
public void add(User user) { // blah-blah-blah }
}
这是Liskov Substitution Principle违规的一个例子吗?如果是,我该如何解决这个问题呢?
答案 0 :(得分:0)
由于User
是Entity
的子类型,因此将此类对象添加到BaseCollection
(通过UserCollection
)是完全合理的 - 每个用户都是{{1} }
将Entity
传递到预期UserCollection
的位置,另一方面则不起作用:您可以预期能够添加BaseCollection
,但需要Entity
- 或换句话说:当你从User
中获得一个元素时,在此之后你可能会得到一个UserCollection
,你期望Entity
。
答案 1 :(得分:0)
违反Liskov替换原则,因为其他实体实体无法添加到UserCollection中。具有BaseCollection引用的用户如果提供User不是User的实体,则不会期望UserCollections的实现会爆炸。
我假设UserCollection.add正在替换BaseCollection.add,因为你明确提到了缩小并且没有指定语言。
如果您遵循Liskov替换原则,方法参数应该是逆变的,而不是协变的。 http://en.wikipedia.org/wiki/Liskov_substitution_principle
答案 2 :(得分:0)
如果BaseCollection
的合同指定其add
方法可以合法地传递来自Entity
的任何对象,则继承add
UserCollection
方法UserCollection
应该这样做,如果不这样做将违反LSP。让add
包含User
的重载(不覆盖)只接受add
类型的对象,如果原始Entity
不会违反LSP方法可以与从add
派生的任意对象一起使用,但重载可能不是特别合适。
如果有问题的方法不是setItem(int index, Entity value)
,而是基类中的setItem(int index, User value)
和派生类中的UserCollection
,并且合同指定它只保证使用从同一个集合中读出的对象,然后只要读取User
就不会产生除setItem
的实例以外的任何内容,{{1在不违反LSP的情况下,方法可以合法地拒绝所有不是User
实例的对象。如果setItem
方法将拒绝所有不是user
实例的内容,那么拥有仅接受user
的重载可能是有用且合适的;即使继承的setItem
方法需要验证value
标识了User
的实例,但是接受该类型的参数的重载也不会。添加这样的重载时最大的警告是应该避免使用两个未密封的虚拟方法来执行相同的操作;如果要添加重载,则应该覆盖并密封基类方法,以便将传入的参数转换为类型User
,然后链接到方法的重载版本。
请注意,数组订阅后一种形式的合同和继承;类型Animal[]
的变量可以包含对Cat[]
的引用;尝试将Animal
存储到标识Animal[]
的{{1}}中的任意Cat[]
可能会失败,但Animal
中Animal[]
的任何{{1}}读取都会保证“适合“在同一个阵列中;这使得代码可以对任意引用类型的数组中的元素进行排序或置换,而无需知道所讨论的数组的类型。