2012-04-17 47 views
1

我有这样的代码:正在缩小的重写方法参数类型Liskov替换原则违反?

abstract class Entity 
{ 
// blah-blah-blah 
} 

abstract class BaseCollection 
{ 
    public void add(Entity entity); 
} 

而且我从实体和BaseCollection类派生:

class User extends Entity 
{ 
} 

class UserCollection extends BaseCollection 
{ 
    public void add(User user) { // blah-blah-blah } 
} 

这是里氏替换原则违反的例子吗?如果是这样,我该如何解决这个问题?

回答

0

作为UserEntity亚型它是完全合理的,例如对象添加到BaseCollection(经由UserCollection) - 每个用户是Entity

传递UserCollection其中BaseCollection预计,不会在另一方面工作:您希望能够添加一个Entity,但您需要一个User - 或者换句话说:当您从UserCollection中获取元素时,您可能会在此之后得到一个Entity,期望User

+0

感谢您的回答。 根据我的经验,当需要使overriden方法的参数类型变窄时,经常会出现这种情况。但是这违反了LSP。 那么我该如何解决这个问题呢? – 2012-04-19 19:05:13

+0

如果你发现自己违反了LSP,那意味着你并没有真正的关系,所以继承不是正确的选择。考虑构图,某种模板/泛型解决方案或仅仅是两个非相关的类而不是 – Attila 2012-04-20 13:53:38

+0

好,但有时您需要模拟一些真实的生活情况。 例如,有'呼叫(博士医生)'方法的类'人'''。而'Child'类扩展了'Human'。但是'孩子'不能接受任何医生。他只需要'延伸'医生'的儿科医生。所以'孩子'这样覆盖方法:'打电话(儿科医生)'。 “孩子”显然与“人”有“关系”。但是,这个类模型违反了LSP。如何克服违规? – 2012-04-21 18:43:00

0

这是对Liskov替换原则的违反,因为实体的其他实现无法添加到UserCollection中。引用BaseCollection的用户不会期望如果UserCollections提供的实体不是用户,而是UserCollections爆炸的实现。

我假设UserCollection.add正在替代BaseCollection.add,正如您明确提到的缩小范围并且未指定语言。

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

+0

“用户的其他实现无法添加”LSP认为Base上的一个属性应该保留在Derived上:对于User而言,Entity是真的。但是,用户的另一个实现的属性不需要保留'用户' – Attila 2012-04-17 19:32:52

+0

“用户”应该是该句子中的“实体”。谢谢你的收获。 – 2012-04-18 13:09:51

0

如果BaseCollection合同指定其add方法可以合法地传递从Entity派生的任何对象,则UserCollection继承add方法也应该这样做,并且如果不这样做将违反了LSP。具有UserCollection包含的add一个过载(未覆盖),其只接受User类型的对象将不违反LSP如果原始add方法可以与从Entity衍生任意对象被使用,虽然过载可能不会是特别合适的。

如果,而不是add,有问题的方法已经像在基setItem(int index, Entity value)setItem(int index, User value)在派生类中,如果合同中所指定它只能保证与已读出的对象打交道相同的集合,然后假定读取UserCollection绝不会产生除User的实例之外的任何东西,setItem方法可合法地拒绝所有不是User的实例而不违反LSP的对象。如果setItem方法将要拒绝所有不是user的实例,那么具有仅接受user的超载可能是有用和适当的;即使继承的setItem方法需要验证value标识User的实例,但接受该类型参数的重载不会。添加这样一个重载时最大的缺点是应该避免使用两个未封装的虚拟方法来做同样的事情;如果有人要添加一个重载,你可能应该重写和封装基类方法,以便它将传入的参数转换为类型User,然后链接到该方法的重载版本。

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

相关问题