gpt4 book ai didi

inheritance - 为什么将实例声明为父类(super class)型却将其实例化为子类型,加上里氏替换原则

转载 作者:行者123 更新时间:2023-12-04 06:35:59 26 4
gpt4 key购买 nike

几天来,我一直在尝试理解 Liskov 替换原则,在使用非常典型的 Rectangle/Square 示例进行一些代码测试时,我创建了下面的代码,并提出了 2 个关于它的问题。

问题 1:如果我们有父类(super class)/子类关系,为什么我们要将一个实例声明为父类(super class)型,但将其实例化(新建)为子类型?

我理解为什么,如果我们通过接口(interface)进行多态性,我们希望以这种方式声明和实例化变量:

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:开闭原则指出我们应该通过新的类(继承或接口(interface))引入新的行为/功能。因此,例如,如果我在基类中有一个 WriteLog 方法,它没有先决条件,但是我引入了一个新的子类,它覆盖了该方法,但只有在事件非常关键时才实际写入日志....如果这是新的预期功能(对子类型进行强化的前提条件),这仍然会破坏 LSP 吗?在这种情况下,这两个原则似乎相互矛盾。

提前致谢。

最佳答案

Question 1: If we have a superclass/subclass relationship, why would we want to declare an instance as the supertype but instantiate it (new it up) as the subtype?



使用父类(super class)型执行此操作的原因与使用接口(interface)执行此操作的原因相同。您列出的为什么将变量声明为其特定子类型而不是父类(super class)型的所有原因同样适用于为什么将变量声明为其特定子类型而不是子类型实现的接口(interface)。
abstract class Car { ... }
public abstract class ToyotaCamery2011 extends Car ( ... )

class Garage {
private Car car = new ToyotaCamery2011();
public Car getCar() { return car; }
....
}

class Garage {
private ToyotaCamery2011 toyotaCamery2011 = new ToyotaCamery2011();
public Car getCar() { return toyotaCamery2011; }
....
}

只要 Garage的所有方法只使用 Car 的方法,以及 Garage的公共(public)接口(interface)只显示 Car并没有特定于 Prius2011 ,这两个类实际上是等价的。哪个更容易理解,例如哪一个更接近现实世界?这可以确保我不会意外使用 Prius 专用方法,即 build 一个 Prius 专用车库?如果我决定买一辆新车,哪一个更容易维护?代码是否使用特定子类型以任何方式改进?

Question 2: So, why or how would this code sample be breaking the LSP? Is it only because of the Square invariant of all sides being equal breaks the Rectangle invariant that sides can be modified independently? If that's the reason, then the LSP violation would be theoretical only? Or how, in code, could I see this code breaking the principle?



不谈论 promise /契约(Contract)就很难谈论 LSP。但是是的,如果 Rectangle promise 边可以独立修改(更正式地说,如果调用 Rectangle.setWidth() 的后置条件包括 Rectangle.getHeight() 应该不受影响),那么 Square源自 Rectangle打破 LSP。

你的程序不依赖于这个属性,所以它很好。但是,采用一个试图满足周长值或面积值的程序。这样的程序可能依赖于 Rectangle 的想法。有独立的边。

任何接受 Rectangle 的类作为输入并取决于 Rectangle 的此属性/行为给定 Square 时可能会中断作为输入。像这样的程序可以跳过箍来寻找和禁止 Square (这是一个子类的知识)或者它可以改变 Rectangle的契约(Contract)关于独立尺寸。然后所有使用 Rectangle 的程序可以在每次调用 setWidth() 后查看或 setLength() to see whether the adjacent side also changed and react accordingly. If it does the latter, than广场 deriving frmo Rectangle` 不再违反 LSP。

它不仅仅是理论上的,它可以对软件产生真正的影响,但在实践中经常会受到影响。不幸的是,您经常在 Java 中看到这一点。 Java的 Iterator类提供 remove()可选的方法。使用迭代器的类必须了解实现类和/或其子类,才能知道使用它是否安全 Iterator.remove() .这违反了 LSP,但它在 Java 中被接受。它使编写和维护软件更复杂,更容易受到错误的影响。

Question 3: The Open-Closed principle states that we should introduce new behavior/functionality through new classes (inheritance or interfaces). So if for example, I have a WriteLog method in the base class, which has no preconditions, but I introduce a new subclass which overrides the method but ONLY actually writes to the log if the event is highly critical....if this is new intended functionality (precondition being hardened on the subtype), would that still be breaking the LSP? The two principles would appear to contradict one another in this case.



当您说先决条件时,我认为您的意思是后置条件-您正在描述该方法 promise 实现的内容。如果是这样,那么我看不到 LSP 违规 - 如果方法父类(super class)什么都不 promise ,那么子类可以做它喜欢的事情并且仍然可以完全替代。子类对它所写的内容更具选择性(“仅实际写入”)这一事实是新功能,特别是考虑到父类(super class)什么都不 promise 的事实。

关于inheritance - 为什么将实例声明为父类(super class)型却将其实例化为子类型,加上里氏替换原则,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4871618/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com