(此答案已于2013-05-13重写,请阅读评论底部的讨论)
LSP 是关于遵循基类的契约。
例如,您可以不在子类中抛出新的异常,因为使用基类的异常不会发生这种情况。如果基类抛出同样的情况ArgumentNullException
如果缺少参数并且子类允许该参数为空,则也是 LSP 违规。
下面是一个违反 LSP 的类结构的示例:
public interface IDuck
{
void Swim();
// contract says that IsSwimming should be true if Swim has been called.
bool IsSwimming { get; }
}
public class OrganicDuck : IDuck
{
public void Swim()
{
//do something to swim
}
bool IsSwimming { get { /* return if the duck is swimming */ } }
}
public class ElectricDuck : IDuck
{
bool _isSwimming;
public void Swim()
{
if (!IsTurnedOn)
return;
_isSwimming = true;
//swim logic
}
bool IsSwimming { get { return _isSwimming; } }
}
以及调用代码
void MakeDuckSwim(IDuck duck)
{
duck.Swim();
}
正如您所看到的,有两个鸭子的例子。一只有机鸭子和一只电动鸭子。电动鸭子只有在打开的情况下才能游泳。这打破了 LSP 原则,因为必须打开它才能像IsSwimming
(这也是合同的一部分)不会像基类中那样设置。
你当然可以通过这样做来解决它
void MakeDuckSwim(IDuck duck)
{
if (duck is ElectricDuck)
((ElectricDuck)duck).TurnOn();
duck.Swim();
}
但这会破坏开放/封闭原则,并且必须在任何地方实现(因此仍然会生成不稳定的代码)。
正确的解决方案是自动打开鸭子Swim
方法,并通过这样做使电动鸭子的行为完全按照定义IDuck
界面
Update
有人添加了评论并删除了它。我想提出一个有效的观点:
解决方案是打开鸭子里面Swim
方法在实际实现时可能会产生副作用(ElectricDuck
)。但这可以通过使用来解决显式接口实现。恕我直言,如果不打开它,您更有可能会遇到问题Swim
因为预计它在使用时会游泳IDuck
界面
Update 2
重新表述了一些部分,使其更加清晰。