你能用一个好的 C# 例子来解释 Liskov 替换原则吗? [英] Can you explain Liskov Substitution Principle with a good C# example?

查看:20
本文介绍了你能用一个好的 C# 例子来解释 Liskov 替换原则吗?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

你能用一个很好的 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆