几天来,我一直在尝试理解 Liskov 替换原则,在使用非常典型的 Rectangle/Square 示例进行一些代码测试时,我创建了下面的代码,并提出了 2 个关于它的问题。
问题 1:如果我们有超类/子类关系,为什么我们要将一个实例声明为超类型,但将其实例化(新建)为子类型?
我理解为什么,如果我们通过接口进行多态性,我们希望以这种方式声明和实例化变量:
IAnimal dog = new Dog();
但是,现在我在旧的编程类和一些博客示例中回忆起它,当通过继承使用多态性时,我仍然会看到一些示例,其中一些代码会以这种方式声明变量
Animal dog = new Dog();
在我下面的代码中,Square 继承自 Rectangle,所以当我以这种方式创建一个新的 Square 实例时:
Square sq = new Square();
它仍然可以被视为一个矩形,或者添加到一个通用的矩形列表中,那么为什么有人仍然想将它声明为 Rectangle = new Square() 呢?是否有我没有看到的好处,或者需要这样做的场景?就像我说的,我下面的代码工作得很好。
namespace ConsoleApp
{
class Program
{
static void Main(string[] args)
{
var rect = new Rectangle(300, 150);
var sq = new Square(100);
Rectangle liskov = new Square(50);
var list = new List<Rectangle> {rect, sq, liskov};
foreach(Rectangle r in list)
{
r.SetWidth(90);
r.SetHeight(80);
r.PrintSize();
r.PrintMyType();
Console.WriteLine("-----");
}
Console.ReadLine();
}
public class Rectangle
{
protected int _width;
protected int _height;
public Rectangle(int width, int height)
{
_width = width;
_height = height;
}
public void PrintMyType()
{
Console.WriteLine(this.GetType());
}
public void PrintSize()
{
Console.WriteLine(string.Format("Width: {0}, Height: {1}", _width, _height));
}
public virtual void SetWidth(int value)
{
_width = value;
}
public virtual void SetHeight(int value)
{
_height = value;
}
public int Width { get { return _width; } }
public int Height { get { return _height; } }
}
public class Square : Rectangle
{
public Square(int size) : base(size, size) {}
public override void SetWidth(int value)
{
base.SetWidth(value);
base.SetHeight(value);
}
public override void SetHeight(int value)
{
base.SetHeight(value);
base.SetWidth(value);
}
}
}
}
尽管这应该违反 Liskov 替换原则,但我得到以下输出:
"宽度:90,高度:80
ConsoleApp.Program+矩形
宽度:80,高度:80
ConsoleApp.Program+Square
宽度:80,高度:80 ConsoleApp.Program+Square
问题 2:那么,此代码示例为何或如何破坏 LSP?仅仅是因为所有边相等的 Square 不变量打破了 Rectangle 不变量,边可以独立修改吗?如果是这个原因,那么 LSP 违规只是理论上的吗?或者,在代码中,我怎么能看到这个代码违反了原则?
编辑:在我正在阅读的一篇 LSP 博客文章中提出了第三个问题,但没有答案,所以就是这个
问题 3:开闭原则指出我们应该通过新的类(继承或接口)引入新的行为/功能。因此,例如,如果我在基类中有一个 WriteLog 方法,它没有先决条件,但是我引入了一个新的子类,它覆盖了该方法,但只有在事件非常关键时才实际写入日志....如果这是新的预期功能(对子类型进行强化的前提条件),这仍然会破坏 LSP 吗?在这种情况下,这两个原则似乎相互矛盾。
提前致谢。