当前位置: 首页 > 面试题库 >

如何避免违反Liskov替代原则(LSP)?

茅秦斩
2023-03-14
问题内容

我的情况与Code Complete中Steve
McConnell
提到的情况非常相似。我唯一的问题是基于车辆,而三轮车恰好是根据法律,属于汽车。到目前为止,汽车只有四个轮子。无论如何,我的域都不必要地复杂,因此很容易遵循下面的cats示例。

对重写例程并且在派生例程中不执行任何操作的类要保持怀疑。这通常表明基类的设计存在错误。例如,假设您有一个Cat类和一个例程Scratch(),并假定您最终发现某些猫被剥皮并且无法抓挠。您可能很想创建一个从Cat派生的名为ScratchlessCat的类,并重写Scratch()例程以执行任何操作。这种方法存在几个问题:

通过更改其接口的语义,它违反了Cat类中提供的抽象(接口协定)。

当您将其扩展到其他派生类时,这种方法很快就会失去控制。如果您发现没有尾巴的猫会怎样?还是不抓老鼠的猫?还是不喝牛奶的猫?最终,您将得到派生类,例如ScratchlessTaillessMicelessMilklessCat。

随着时间的流逝,这种方法会导致代码难以维护,因为祖先类的接口和行为对后代的行为几乎没有暗示。

解决此问题的位置不在基类中,而在原始Cat类中。创建一个Claws类,并将其包含在Cats类中。根本问题是所有猫都抓挠猫的假设,因此,应从源头上解决该问题,而不仅仅是在目标位置进行包扎。

根据他以上伟大著作的文字。跟随不好

父类不必是抽象的

public abstract class Cat {
   public void scratch() {
      System.out.println("I can scratch");
   }
}

派生类

public class ScratchlessCat extends Cat {
   @Override
   public void scratch() {
      // do nothing
   }
}

现在,他建议创建另一个类Claws,但我不知道如何使用该类来避免需要ScratchlessCat#Scratch


问题答案:

您仍然会有一个scratch()方法,但不会被派生类覆盖:

public class Cat {
  Claw claw_;
  public Cat(Claw claw) {claw = claw_;}
  public final void scratch() {
    if (claw_ != null) {
      claw_.scratch(this);
    }
  }
}

这使您可以将抓取逻辑委托给所包含的Claw对象(如果存在)(如果没有爪,则不进行抓取)。从cat派生的类在如何刮擦方面没有发言权,因此无需根据能力创建影子层次结构。

同样,由于派生类无法更改方法的实现,因此它们不会破坏scratch()基类接口中方法的预期语义的问题。

如果将其极端化,您可能会发现您有很多类,而没有很多派生-大多数逻辑都委托给组合对象,而不委托给派生类。



 类似资料:
  • 来自维基百科, Liskov的行为子类型概念定义了对象的可替代性概念;也就是说,如果S是T的子类型,则程序中T类型的对象可以替换为S类型的对象,而不改变该程序的任何期望属性(例如正确性)。 假设以下类层次结构: 基本抽象类-。它有一个只读属性,在后继程序中被重写。 基类的继承者-,它重写并返回灰色。 Cat的继任者-,它覆盖并返回带条纹的。 然后我们声明一个方法,参数类型为(不是)。 向该方法发送

  • 我是OOP的新手。最近我读到了关于Liskov替换原理的文章。 在下面给出的代码中,Square类继承Give_区域。假设Square类与Square相关(比如有效性检查)。Give_Area给出正方形的面积(4个顶点位于圆的周长上)和圆的面积。所以,如果给我一个半径,我必须打印圆和正方形的面积(由放置在圆周长上的顶点组成)。为了得到圆的面积,我使用了一个参数。但在求平方面积时没有参数。因此,我在

  • 有人能告诉我下面的例子是否违反了LSP吗? 我有一个例子: 和子类: 和主类: 在此示例中,子类添加名为 的新属性,并通过对其自己的属性 进行附加检查来覆盖方法。 在main方法中,我创建了2个对象。第一个是类型的对象,第二个是类型的对象。 当验证人员时,因为所有前提条件都是正确的,所以它是正确的,但是对于员工,它将抛出< code > IllegalArgumentException ,因为它与

  • 如果S是T的一个子类型,那么T类型的对象可以被S类型的对象替换。 子类有两种不同的行为(选中与未选中),在某些情况下,除非更改当前代码,否则无法用子类对象有效地替换基类用法,例如,如果编写如下代码: 这是违反吗?,为什么/为什么不?。 资料来源:http://www.oracle.com/technetwork/articles/entarch/effective-exceptions-09234

  • 我在一个程序中遇到了一个设计问题,这是由于一个基有一个带有一个位置(因而是可选)参数的方法。 我们假设这个类是,方法是。现在,是可选的,因为我已经知道的一些子类将使用它,其中一些不会。 实际的问题是违反Liskov替换原则:在需要的子类中,如果没有提供,我必须抛出异常,而在(未实现)中,我没有抛出任何异常例外。 的设计也很糟糕,因为我提供了一个方法,有些子类确实需要它,有些子类需要稍微不同的版本。