我听说利斯科夫替换原则(LSP)是面向对象设计的基本原则。它是什么?它的一些使用例子是什么?


当前回答

设q(x)是关于类型为T的x的对象的可证明属性,那么q(y)对于类型为S的对象y应该是可证明的,其中S是T的子类型。


实际上,公认的答案并不是利斯科夫原理的反例。正方形自然是一个特定的矩形,因此从类矩形继承是完全有意义的。你只需要以这样的方式实现它:

@Override
public void setHeight(double height) {
   this.height = height;
   this.width = height; // since it's a square
}

@Override
public void setWidth(double width) {
   setHeight(width);
}

所以,提供了一个很好的例子,然而,这是一个反例:

class Family:
-- getChildrenCount()

class FamilyWithKids extends Family:
-- getChildrenCount() { return childrenCount; } // always > 0

class DeadFamilyWithKids extends FamilyWithKids:
-- getChildrenCount() { return 0; }
-- getChildrenCountWhenAlive() { return childrenCountWhenAlive; }

在这个实现中,DeadFamilyWithKids不能从FamilyWithKids继承,因为getChildrenCount()返回0,而从FamilyWithKids它应该总是返回大于0的值。

其他回答

以Board数组的形式实现ThreeDBoard会有用吗?

也许你想把不同平面上的ThreeDBoard切片作为一个板。在这种情况下,您可能希望为Board抽象出一个接口(或抽象类),以允许多种实现。

就外部接口而言,您可能希望为TwoDBoard和ThreeDBoard提取一个Board接口(尽管上述方法都不适合)。

利斯科夫替换原则(来自Mark Seemann的书)指出,我们应该能够在不破坏客户端或实现的情况下,用另一个接口的实现替换一个接口的实现。正是这一原则使我们能够解决未来出现的需求,即使我们今天不能预见它们。

If we unplug the computer from the wall (Implementation), neither the wall outlet (Interface) nor the computer (Client) breaks down (in fact, if it’s a laptop computer, it can even run on its batteries for a period of time). With software, however, a client often expects a service to be available. If the service was removed, we get a NullReferenceException. To deal with this type of situation, we can create an implementation of an interface that does “nothing.” This is a design pattern known as Null Object,[4] and it corresponds roughly to unplugging the computer from the wall. Because we’re using loose coupling, we can replace a real implementation with something that does nothing without causing trouble.

它指出,如果C是E的子类型,则E可以替换为C类型的对象,而不会改变或破坏程序的行为。简单地说,派生类应该可以替代它们的父类。例如,如果一个农民的儿子是农民,那么他可以代替他的父亲工作,但如果一个农民的儿子是板球运动员,那么他就不能代替他的父亲工作。

违反的例子:

public class Plane{

  public void startEngine(){}      

}        
public class FighterJet extends Plane{}
    
public class PaperPlane extends Plane{}

在给定的例子中,fighter和PaperPlane类都扩展了包含startEngine()方法的Plane类。所以很明显,战斗机可以启动引擎,但纸飞机不能,所以它破坏LSP。

PaperPlane类虽然扩展了Plane类,但应该可以替代Plane类,但它不是Plane实例可以被替换的合格实体,因为纸飞机不能启动引擎,因为它没有引擎。好的例子是,

受人尊敬的例子:

public class Plane{ 
} 
public class RealPlane{

  public void startEngine(){} 

}
public class FighterJet extends RealPlane{} 
public class PaperPlane extends Plane{}

A square is a rectangle where the width equals the height. If the square sets two different sizes for the width and height it violates the square invariant. This is worked around by introducing side effects. But if the rectangle had a setSize(height, width) with precondition 0 < height and 0 < width. The derived subtype method requires height == width; a stronger precondition (and that violates lsp). This shows that though square is a rectangle it is not a valid subtype because the precondition is strengthened. The work around (in general a bad thing) cause a side effect and this weakens the post condition (which violates lsp). setWidth on the base has post condition 0 < width. The derived weakens it with height == width.

因此,可调整大小的正方形不是可调整大小的矩形。

到目前为止,我发现LSP最清晰的解释是“利斯科夫替换原则说,派生类的对象应该能够替换基类的对象,而不会给系统带来任何错误,也不会修改基类的行为”。文中给出了违反LSP的代码示例并进行了修复。