1

我有这个代码:

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 替换原则的一个例子吗?如果是,我该如何解决这个问题?

4

3 回答 3

0

如果 for 的合约BaseCollection指定其add方法可以合法地传递自 的任何对象Entity,那么继承的add方法 也UserCollection应该这样做,否则将违反 LSP。如果原始方法可以与从 派生的任意对象一起使用,则UserCollection包含仅接受类型对象的重载(而不是覆盖)不会违反 LSP ,尽管重载可能不是特别合适。addUseraddEntity

如果add所讨论的方法不是 ,而是类似于setItem(int index, Entity value)setItem(int index, User value)类和派生类中的方法,并且如果合同规定它只保证与从同一个集合中读取的对象一起工作,那么提供读取除了的UserCollection实例之外永远不会产生任何东西User,该方法可以在不违反 LSPsetItem的情况下合法地拒绝所有不是实例的对象。User如果该setItem方法要拒绝不是 的实例的所有内容user,那么具有仅接受的重载user可能是有用且适当的;即使继承的setItem方法需要验证value标识的实例User,接受该类型参数的重载不会。添加此类重载时最大的警告是应避免使用两个未密封的虚拟方法来执行相同的操作;如果要添加重载,则可能应该覆盖并密封基类方法,以便它将传入的参数转换为类型User,然后链接到方法的重载版本。

请注意,数组订阅后一种形式的契约和继承;一个类型的变量Animal[]可以持有对 a 的引用Cat[];尝试将任意值存储AnimalAnimal[]标识 a 的 a 中Cat[]可能会失败,但任何Animal读出的 anAnimal[]都保证“适合”在同一个数组中;这使得代码可以对任意引用类型的数组中的元素进行排序或置换,而无需知道相关数组的类型。

于 2014-12-26T20:23:34.163 回答
0

作为User一个子类型,Entity将此类对象添加到BaseCollection(via UserCollection) 是完全合理的——每个用户都是Entity

另一方面,通过UserCollectionwhere预期将不起作用:您应该能够添加一个,但您需要一个-- 或者换句话说:当您从 中获取一个元素时,您可能会在此之后得到一个,你期望的地方。BaseCollectionEntityUserUserCollectionEntityUser

于 2012-04-17T19:18:15.967 回答
0

这违反了 Liskov 替换原则,因为实体的其他实现无法添加到 UserCollection。引用 BaseCollection 的用户不会期望作为 UserCollection 的实现在提供用户以外的实体时爆炸。

我假设 UserCollection.add 正在替换 BaseCollection.add ,因为您明确提到了缩小范围并且没有指定语言。

如果您遵循 Liskov 替换原则,方法参数应该是逆变的,而不是协变的。 http://en.wikipedia.org/wiki/Liskov_substitution_principle

于 2012-04-17T19:25:20.833 回答