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


当前回答

到目前为止,我发现LSP最清晰的解释是“利斯科夫替换原则说,派生类的对象应该能够替换基类的对象,而不会给系统带来任何错误,也不会修改基类的行为”。文中给出了违反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说“对象应该被它们的子类型替换”。 另一方面,这一原则指向

子类永远不应该破坏父类的类型定义。

通过以下示例,可以更好地理解LSP。

没有太阳能发电:

public interface CustomerLayout{

    public void render();
}


public FreeCustomer implements CustomerLayout {
     ...
    @Override
    public void render(){
        //code
    }
}


public PremiumCustomer implements CustomerLayout{
    ...
    @Override
    public void render(){
        if(!hasSeenAd)
            return; //it isn`t rendered in this case
        //code
    }
}

public void renderView(CustomerLayout layout){
    layout.render();
}

LSP修复:

public interface CustomerLayout{
    public void render();
}


public FreeCustomer implements CustomerLayout {
     ...
    @Override
    public void render(){
        //code
    }
}


public PremiumCustomer implements CustomerLayout{
    ...
    @Override
    public void render(){
        if(!hasSeenAd)
            showAd();//it has a specific behavior based on its requirement
        //code
    }
}

public void renderView(CustomerLayout layout){
    layout.render();
}

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的有效替代品。

这里有一个清单来确定你是否违反了利斯科夫法则。

如果你违反了以下项目之一->,你违反了里斯科夫。 如果你不违反任何->不能得出任何结论。

检查表:

No new exceptions should be thrown in derived class: If your base class threw ArgumentNullException then your sub classes were only allowed to throw exceptions of type ArgumentNullException or any exceptions derived from ArgumentNullException. Throwing IndexOutOfRangeException is a violation of Liskov. Pre-conditions cannot be strengthened: Assume your base class works with a member int. Now your sub-type requires that int to be positive. This is strengthened pre-conditions, and now any code that worked perfectly fine before with negative ints is broken. Post-conditions cannot be weakened: Assume your base class required all connections to the database should be closed before the method returned. In your sub-class you overrode that method and left the connection open for further reuse. You have weakened the post-conditions of that method. Invariants must be preserved: The most difficult and painful constraint to fulfill. Invariants are sometimes hidden in the base class and the only way to reveal them is to read the code of the base class. Basically you have to be sure when you override a method anything unchangeable must remain unchanged after your overridden method is executed. The best thing I can think of is to enforce these invariant constraints in the base class but that would not be easy. History Constraint: When overriding a method you are not allowed to modify an unmodifiable property in the base class. Take a look at these code and you can see Name is defined to be unmodifiable (private set) but SubType introduces new method that allows modifying it (through reflection): public class SuperType { public string Name { get; private set; } public SuperType(string name, int age) { Name = name; Age = age; } } public class SubType : SuperType { public void ChangeName(string newName) { var propertyType = base.GetType().GetProperty("Name").SetValue(this, newName); } }

还有2项:方法参数的逆变性和返回类型的协方差。但这在c#中是不可能的(我是c#开发人员),所以我不关心它们。

利斯科夫替换原理

被重写的方法不应该保持为空 被重写的方法不应该抛出错误 基类或接口行为不应该因为派生类行为而进行修改(重做)。