65.9K
CodeProject 正在变化。 阅读更多。
Home

理解 Liskov 替换原则( LSP)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.51/5 (32投票s)

2013年5月21日

CPOL

3分钟阅读

viewsIcon

127692

本文将帮助您清楚地理解 LSP。

引言

在这里,我将讨论 SOLID 中的 Liskov 替换原则。那么 SOLID 是什么意思呢?SOLID 是一种面向对象的设计原则,其中每个字母都有其自身的含义。

  • S-> 单一职责
  • O-> 开闭原则
  • L-> Liskov 替换原则
  • I-> 接口隔离原则
  • D-> 依赖倒置原则

根据 Wikipedia,SOLID 的定义是:

"SOLID 是在处理软件时可以应用的一组指导方针,它们通过促使程序员重构软件的源代码,直到源代码既可读又可扩展,从而消除代码异味。"

背景

如果您阅读了我之前的文章,将会非常有助您理解 SOLID。

  1. 理解开闭原则和依赖倒置原则 .
  2. 理解单一职责原则和接口隔离原则

Using the Code

在开始技术讨论之前,我想先回答以下问题:

什么是 Liskov 替换原则?

答案:"程序中的对象应该可以用其子类型的实例来替换,而不会改变该程序的正确性"

我的答案很难理解吗?好的,我将使其更容易理解……

"这意味着我们必须确保新的派生类在扩展基类时不会改变其行为。"

让我们举个例子来更好地理解。如果您阅读过与 Liskov 原则相关的文章,有一个例子非常流行。是的,那就是矩形正方形。在这里,我将向您展示相同的内容,但会尝试清楚地解释每一个方面。在开始技术讨论之前,请看下图并回答问题。

           

图 1

问题:正方形是矩形吗?
答案:是

如果您还记得 继承,那么"是一个"关系这个词对您来说应该很熟悉。所以如果将其转换为代码,看起来如下:

public class Rectangle
{
    protected int _width;
    protected int _height;
    public int Width
    {
        get { return _width; }
    }
    public int Height
    {
        get { return _height; }
    }

    public virtual void SetWidth(int width)
    {
        _width = width;
    }    
    public virtual void SetHeight(int height)
    {
        _height = height;
    }
    public int getArea()
    {
        return _width * _height;
    }
}  

public class Square : Rectangle  // In an "is a" relationship, the derived class is clearly a
                                  //kind of the base class
{
    public override void SetWidth(int width)
    {
        _width = width;
        _height = width;
    }

    public override void SetHeight(int height)
    {
        _height = height;
        _width = height;
    }
}

所以一切都很好。现在我们将计算图 1:RectangleSquare 的面积。

public void AreaOfRectangle()
    {
        Rectangle r = RectangleFactory(); // Returns the rectangle type object
        r.SetWidth(7);
        r.SetHeight(3);
        r.getArea();
    }  

那么,您能告诉我 r.getArea() 方法的输出是什么吗?是的,很简单,预期的输出是 7 * 3 = 21。现在使用下面的 RectangleFactory() 方法运行程序,看看我们是否能得到预期的结果?

public  Rectangle RectangleFactory()
    {
        return new Square();
    }

有一点我想提一下关于 RectangleFactory():这个方法现在向您公开了。但是请设想您只是通过一个工厂 DLL 或某个服务获得了 Rectangle 对象,而您不知道会返回哪种类型的矩形对象。

您看到结果了吗?是的。

这是输出:9

那么问题出在哪里?请记住,正如我之前所说:

"我们必须确保新的派生类在扩展基类时不会改变其行为"

具体来说:

"我们必须确保 Square 类在扩展 Rectangle 时不会改变其行为"

根据我们的程序,上面的说法正确吗?

不,问题在于

不改变其行为

但是我们改变了基类 Rectangle 的行为。您注意到如何改变的吗?没有?好吧,我们来看看。

public override void SetWidth(int width)
    {
        _width = width; 
        _height = width; //Change the behavior here by assigning the width to Rectangle _height
                         
    }

    public override void SetHeight(int height)
    {
        _height = height;
        _width = height;//Change the behavior here by assigning the height to Rectangle _width 
                        
    } 

Width=HeightSquare强制规则,而不是 Rectangle 的。因此,当 Square 对象从 RectangleFactory() 返回时,在调用 getArea() 之前,用户分配了 r.SetHeight(3)r.SetWidth(7),并期望得到结果21,但却得到了9改变了结果。但是最终用户期望的是 Rectangle 面积的结果。

因此,违反了 Liskov 替换原则。

现在解决方案是正确地管理类继承层次结构。让我们引入另一个类。

public abstract class Quadrilaterals
{
    abstract public int GetArea();
}

现在通过使其成为基类来更改继承层次结构。

public class Rectangle :Quadrilaterals
{    
    public int Width
    {
        get ; set ;
    }
    public int Height
    {
        get ; 
        set ;
    }
    public override int GetArea()
    {
        return Height * Width;
    }    
} 
public class Square : Quadrilaterals  // In an "is a" relationship, the derived class is clearly a
                                      // kind of the base class
{
    public  int Size
    {
        get ; 
        set ;
    }
    public override int GetArea()
    {
        return Size* Size;
    }
}  

现在我们的工厂方法也改变了。

 public Quadrilaterals QuadrilateralsFactory()
    {
        return new Square();
    }
    public void AreaOfQuadrilateral()
    {
        Quadrilaterals r = QuadrilateralsFactory(); // Returns the Quadrilaterals type object
        r.Height=7;
        r.Width=3;
        r.getArea();
    } 

关注点

从用户的角度来看,现在他/她期望的是 <code>Quadrilateral 的面积,而不是 Rectangle 的面积。使用此输入,当输出为21时,它是 Rectangle 的面积;当输出为 9 时,它是 Square 的面积。

 Quadrilaterals r = new Rectangle();
        r.Height=7;
        r.Width=3;
        r.getArea();

        Quadrilaterals r = new Square();
        r.Height = 7;
        r.Width = 3;
        r.getArea(); 

现在用户从程序中获得了预期的行为。因此,它满足了Liskov 替换原则 (LSP)

© . All rights reserved.