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


当前回答

利斯科夫替换原理

(固体)

继承子类型化

维基里斯科夫替换原理(LSP)

在子类型中不能加强先决条件。 后置条件不能在子类型中减弱。 超类型的不变量必须保留在子类型中。

子类型不应该要求调用者提供比超类型更多的(先决条件) 子类型不应该为小于超类型的调用者公开(后置条件)

*前置条件+后置条件=函数(方法)类型[Swift函数类型。Swift函数与方法

//Swift function
func foo(parameter: Class1) -> Class2

//function type
(Class1) -> Class2

//Precondition
Class1

//Postcondition
Class2

例子

//C3 -> C2 -> C1

class C1 {}
class C2: C1 {}
class C3: C2 {}

前提条件(如。函数参数类型)可以相同或更弱(力求-> C1) 后置条件(如。函数返回类型)可以相同或更强(力求-> C3) 超类型的不变变量[About]应该保持不变

斯威夫特

class A {
    func foo(a: C2) -> C2 {
        return C2()
    }
}

class B: A {
    override func foo(a: C1) -> C3 {
        return C3()
    }
}

Java

class A {
    public C2 foo(C2 a) {
        return new C2();
    }
}

class B extends A {
    @Override
    public C3 foo(C2 a) { //You are available pass only C2 as parameter
        return new C3();
    }
}

行为子类型化

维基里斯科夫替换原理(LSP)

子类型中方法参数类型的逆变性。子类型中方法返回类型的协方差。 子类型中的方法不能引发新的异常,除非它们是超类型的方法引发的异常的子类型。

[方差,协方差,逆变,不变性]

其他回答

LSP是关于类的契约的规则:如果基类满足契约,则LSP派生的类也必须满足该契约。

在Pseudo-python

class Base:
   def Foo(self, arg): 
       # *... do stuff*

class Derived(Base):
   def Foo(self, arg):
       # *... do stuff*

如果每次在派生对象上调用Foo,它给出的结果与在Base对象上调用Foo完全相同,只要arg是相同的。

关于LSP的一个很好的例子(在我最近听到的播客中,Bob叔叔给出了一个例子)是,有时候在自然语言中听起来正确的东西在代码中却不太适用。

在数学中,正方形是长方形。实际上,它是矩形的专门化。“is a”使您想用继承来建模。然而,如果在代码中你从Rectangle派生出Square,那么Square应该可以在任何你想要Rectangle的地方使用。这就导致了一些奇怪的行为。

假设你在你的Rectangle基类上有SetWidth和SetHeight方法;这似乎完全合乎逻辑。然而,如果你的矩形引用指向一个正方形,那么SetWidth和SetHeight没有意义,因为设置一个会改变另一个来匹配它。在这种情况下,Square未能通过矩形的利斯科夫替换测试,并且让Square继承Rectangle的抽象是一个糟糕的抽象。

你们都应该看看其他用励志海报解释的无价的坚实原则。

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。

没有太阳能发电:

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();
}