我听说利斯科夫替换原则(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的值。

其他回答

LSP的这种形式太强大了:

如果对于每个类型为S的对象o1,都有一个类型为T的对象o2,使得对于所有用T定义的程序P,当o1取代o2时,P的行为不变,那么S是T的子类型。

这基本上意味着S是t的另一个完全封装的实现,我可以大胆地认为性能是P行为的一部分……

因此,基本上,任何延迟绑定的使用都违反了LSP。当我们用一种类型的对象替换另一种类型的对象时,获得不同的行为是OO的全部意义所在!

维基百科引用的公式更好,因为属性取决于上下文,并不一定包括程序的整个行为。

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关注不变量。

经典示例由以下伪代码声明给出(实现略):

class Rectangle {
    int getHeight()
    void setHeight(int value) {
        postcondition: width didn’t change
    }
    int getWidth()
    void setWidth(int value) {
        postcondition: height didn’t change
    }
}

class Square extends Rectangle { }

现在我们有一个问题,尽管接口匹配。原因是我们违反了源自正方形和矩形数学定义的不变量。getter和setter的工作方式,矩形应该满足以下不变量:

void invariant(Rectangle r) {
    r.setHeight(200)
    r.setWidth(100)
    assert(r.getHeight() == 200 and r.getWidth() == 100)
}

然而,Square的正确实现必须违反这个不变量(以及显式后置条件),因此它不是Rectangle的有效替代品。

在一个非常简单的句子中,我们可以说:

子类不能违背它的基类特征。它必须有能力。我们可以说这和子类型是一样的。