14

The LSP says "The derived types must not change the behavior of the base types", in other words "Derived types must be completely replaceable for their base types."

This means that if we define virtual methods in our base classes, we have violated this principle.

Also if we hide a method in the drive method by using new keyword then again we have violated this principle.

In other words, if we use polymorphism we have violated LSP!

In many applications I've used Virtual methods in the base classes and now I realize it violates LSP. Also if you use Template Method pattern you have violated this principle that I've used it a lot.

So, how to design your application that complies with this principle when you'd need inheritance and you'd like to benefit also from polymorphism? I'm confused!

See the example from here: http://www.oodesign.com/liskov-s-substitution-principle.html

4

6 回答 6

9

Barbara Liskov 有一篇非常好的文章Data Abstraction and Hierarchy,她在其中特别涉及多态行为和虚拟软件结构。阅读本文后,您可以看到,她深入描述了软件组件如何通过简单的多态调用实现灵活性和模块化。

LSP 陈述的是实现细节,而不是抽象。具体来说,如果您使用 type 的某些接口或抽象T,您应该期望传递 of 的所有子类型,T而不是观察到意外行为或程序崩溃。

这里的关键字是意想不到的,因为它可以描述程序的任何属性(正确性、执行的任务、返回的语义、临时性等)。所以让你方法virtual本身并不意味着违反LSP

于 2013-01-18T17:02:33.167 回答
3

“派生类型不得改变基类型的行为”意味着必须可以像使用基类型一样使用派生类型。例如,如果你能打电话x = baseObj.DoSomeThing(123),你也必须能打电话x = derivedObj.DoSomeThing(123)。如果基方法没有,派生方法不应该抛出异常。使用基类的代码也应该能够很好地与派生类一起工作。它不应该“看到”它正在使用另一种类型。这并不意味着派生类必须做完全相同的事情。那将毫无意义。换句话说,使用派生类型不应该破坏使用基类型顺利运行的代码。

作为示例,假设您声明了一个记录器,使您能够将消息记录到控制台

logger.WriteLine("hello");

您可以在需要生成日志的类中使用构造函数注入。现在,不是将控制台记录器传递给它,而是将它传递给从控制台记录器派生的文件记录器。如果文件记录器抛出异常说“您必须在消息字符串中包含行号”,这将破坏 LSP。但是,日志记录到文件而不是控制台不是问题。即,如果记录器向调用者显示相同的行为,则一切正常。


如果您需要编写如下代码,则将违反 LSP:

if (logger is FileLogger) {
    logger.Write("10 hello"); // FileLogger requires a line number

    // This throws an exception!
    logger.Write("hello");
} else {
    logger.Write("hello");
}

顺便说一句:new关键字不影响多态性,而是声明了一个全新的方法,该方法恰好与基类型中的方法同名,但与之无关。特别是,不可能通过基本类型来调用它。要使多态起作用,您必须使用override关键字并且方法必须是虚拟的(除非您正在实现接口)。

于 2013-01-18T17:11:21.900 回答
2

我认为 Liskov 的替换原则 (LSP) 主要是关于将可能与子类不同的函数的实现移动到尽可能通用的父类。

因此,无论您在子类中进行什么更改,只要此更改不会强制您修改父类中的代码,它就不会违反 Liskov 替换原则 (LSP)。

于 2013-01-18T16:59:09.937 回答
2

LSP 说您必须能够以与使用它的超类相同的方式使用派生类:“程序中的对象应该可以替换为其子类型的实例,而不会改变该程序的正确性”。打破该规则的经典继承是从 Rectangle 类派生 Square 类,因为前者必须具有Height = Width,而后者可以具有Height != Width

public class Rectangle
{
    public virtual Int32 Height { get; set; }
    public virtual Int32 Width { get; set; }
}

public class Square : Rectangle
{
    public override Int32 Height
    {
        get { return base.Height; }
        set { SetDimensions(value); }
    }

    public override Int32 Width
    {
        get { return base.Width; }
        set { SetDimensions(value); }
    }

    private void SetDimensions(Int32 value)
    {
        base.Height = value;
        base.Width = value;
    }
}

在这种情况下,Width 和 Height 属性的行为发生了变化,这违反了该规则。让我们看看输出为什么会改变:

private static void Main()
{
    Rectangle rectangle = new Square();
    rectangle.Height = 2;
    rectangle.Width = 3;

    Console.WriteLine("{0} x {1}", rectangle.Width, rectangle.Height);
}

// Output: 3 x 2
于 2013-01-18T17:01:56.203 回答
0

子类型必须可以被基类型替换。

在接触方面。

派生类可以替换相同或更弱的基类前置条件和相同或更大的后置条件。

关联

于 2013-01-18T17:02:02.810 回答
-1

为了使多态性起作用,必须遵守 LSP。打破它的一个好方法是在派生类型中引入不在基类型中的方法。在这种情况下,多态性不起作用,因为这些方法在基类型中不可用。您可以对方法有不同的子类型实现,同时遵守多态性和 LSP。

于 2013-01-18T17:03:10.713 回答