理解 Liskov 替换原则( LSP)






4.51/5 (32投票s)
本文将帮助您清楚地理解 LSP。
引言
在这里,我将讨论 SOLID 中的 Liskov 替换原则。那么 SOLID 是什么意思呢?SOLID 是一种面向对象的设计原则,其中每个字母都有其自身的含义。
- S-> 单一职责
- O-> 开闭原则
- L-> Liskov 替换原则
- I-> 接口隔离原则
- D-> 依赖倒置原则
根据 Wikipedia,SOLID 的定义是:
"SOLID 是在处理软件时可以应用的一组指导方针,它们通过促使程序员重构软件的源代码,直到源代码既可读又可扩展,从而消除代码异味。"
背景
如果您阅读了我之前的文章,将会非常有助您理解 SOLID。
Using the Code
在开始技术讨论之前,我想先回答以下问题:
什么是 Liskov 替换原则?
答案:"程序中的对象应该可以用其子类型的实例来替换,而不会改变该程序的正确性"
我的答案很难理解吗?好的,我将使其更容易理解……
"这意味着我们必须确保新的派生类在扩展基类时不会改变其行为。"
让我们举个例子来更好地理解。如果您阅读过与 Liskov 原则相关的文章,有一个例子非常流行。是的,那就是矩形和正方形。在这里,我将向您展示相同的内容,但会尝试清楚地解释每一个方面。在开始技术讨论之前,请看下图并回答问题。
问题:正方形是矩形吗?
答案:是
如果您还记得 继承,那么"是一个"关系这个词对您来说应该很熟悉。所以如果将其转换为代码,看起来如下:
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:Rectangle
和 Square
的面积。
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=Height
是 Square
的强制规则,而不是 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)。