你能用一个好的 C# 例子来解释 Liskov 替换原则吗? [英] Can you explain Liskov Substitution Principle with a good C# example?
问题描述
你能用一个很好的 C# 示例来解释 Liskov 替换原则(SOLID 的L"),以简化的方式涵盖该原则的所有方面吗?如果真的有可能.
(此答案已于 2013-05-13 重写,请阅读评论底部的讨论)
LSP 是关于遵循基类的契约.
例如,您不能在子类中抛出新的异常,因为使用基类的人不会想到这一点.如果基类抛出 ArgumentNullException
如果缺少参数并且子类允许该参数为空,则同样适用,这也是违反 LSP 的.
以下是违反 LSP 的类结构示例:
公共接口IDuck{无效游泳();//合同规定,如果 Swim 被调用,则 IsSwimming 应该为真.bool IsSwimming { 得到;}}公共类有机鸭:IDuck{公共无效游泳(){//做一些游泳的事情}bool IsSwimming { get {/* 如果鸭子在游泳,则返回 */} }}公共类 ElectricDuck:IDuck{bool _isSwimming;公共无效游泳(){如果 (!IsTurnedOn)返回;_isSwimming = 真;//游泳逻辑}bool IsSwimming { get { return _isSwimming;} }}
和调用代码
void MakeDuckSwim(IDuck 鸭){鸭子游泳();}
如您所见,有两个鸭子示例.一只有机鸭和一只电鸭.电动鸭子只有打开才能游泳.这违反了 LSP 原则,因为必须打开它才能游泳,因为 IsSwimming
(这也是合同的一部分)不会像在基类中那样设置.
你当然可以通过做这样的事情来解决它
void MakeDuckSwim(IDuck 鸭){如果(鸭子是 ElectricDuck)((ElectricDuck)duck).TurnOn();鸭子游泳();}
但这会破坏开放/封闭原则,并且必须在任何地方实施(因此仍然会生成不稳定的代码).
正确的解决方案是在 Swim
方法中自动打开鸭子,通过这样做使电鸭的行为与 IDuck
接口定义的完全一样>
更新
有人添加了评论并删除了它.我想说明一个有效的观点:
在Swim
方法中打开duck 的解决方案在使用实际实现(ElectricDuck
) 时可能会产生副作用.但这可以通过使用显式接口实现来解决.恕我直言,在 Swim
中不打开它更有可能出现问题,因为在使用 IDuck
界面时预计它会游泳
更新 2
改写了一些部分以使其更清晰.
Can you explain Liskov Substitution Principle (The 'L' of SOLID) with a good C# example covering all aspects of the principle in a simplified way? If it is really possible.
(This answer has been rewritten 2013-05-13, read the discussion in the bottom of the comments)
LSP is about following the contract of the base class.
You can for instance not throw new exceptions in the sub classes as the one using the base class would not expect that. Same goes for if the base class throws ArgumentNullException
if an argument is missing and the sub class allows the argument to be null, also a LSP violation.
Here is an example of a class structure which violates 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; } }
}
And the calling code
void MakeDuckSwim(IDuck duck)
{
duck.Swim();
}
As you can see, there are two examples of ducks. One organic duck and one electric duck. The electric duck can only swim if it's turned on. This breaks the LSP principle since it must be turned on to be able to swim as the IsSwimming
(which also is part of the contract) won't be set as in the base class.
You can of course solve it by doing something like this
void MakeDuckSwim(IDuck duck)
{
if (duck is ElectricDuck)
((ElectricDuck)duck).TurnOn();
duck.Swim();
}
But that would break Open/Closed principle and has to be implemented everywhere (and thefore still generates unstable code).
The proper solution would be to automatically turn on the duck in the Swim
method and by doing so make the electric duck behave exactly as defined by the IDuck
interface
Update
Someone added a comment and removed it. It had a valid point that I'd like to address:
The solution with turning on the duck inside the Swim
method can have side effects when working with the actual implementation (ElectricDuck
). But that can be solved by using a explicit interface implementation. imho it's more likely that you get problems by NOT turning it on in Swim
since it's expected that it will swim when using the IDuck
interface
Update 2
Rephrased some parts to make it more clear.
这篇关于你能用一个好的 C# 例子来解释 Liskov 替换原则吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!