我听说利斯科夫替换原则(LSP)是面向对象设计的基本原则。它是什么?它的一些使用例子是什么?
当前回答
可替代性是面向对象编程中的一个原则,它指出,在计算机程序中,如果S是T的子类型,那么类型T的对象可以被类型S的对象替换
让我们用Java做一个简单的例子:
不好的例子
public class Bird{
public void fly(){}
}
public class Duck extends Bird{}
鸭子能飞,因为它是鸟,但这个呢:
public class Ostrich extends Bird{}
鸵鸟是一种鸟,但它不能飞,鸵鸟类是鸟类的一个子类,但它不应该能够使用fly方法,这意味着我们打破了LSP原则。
很好的例子
public class Bird{}
public class FlyingBirds extends Bird{
public void fly(){}
}
public class Duck extends FlyingBirds{}
public class Ostrich extends Bird{}
其他回答
罗伯特·马丁有一篇关于利斯科夫替换原理的优秀论文。它讨论了可能违反原则的微妙和不那么微妙的方式。
论文的一些相关部分(注意,第二个例子被大量压缩):
A Simple Example of a Violation of LSP One of the most glaring violations of this principle is the use of C++ Run-Time Type Information (RTTI) to select a function based upon the type of an object. i.e.: void DrawShape(const Shape& s) { if (typeid(s) == typeid(Square)) DrawSquare(static_cast<Square&>(s)); else if (typeid(s) == typeid(Circle)) DrawCircle(static_cast<Circle&>(s)); } Clearly the DrawShape function is badly formed. It must know about every possible derivative of the Shape class, and it must be changed whenever new derivatives of Shape are created. Indeed, many view the structure of this function as anathema to Object Oriented Design. Square and Rectangle, a More Subtle Violation. However, there are other, far more subtle, ways of violating the LSP. Consider an application which uses the Rectangle class as described below: class Rectangle { public: void SetWidth(double w) {itsWidth=w;} void SetHeight(double h) {itsHeight=w;} double GetHeight() const {return itsHeight;} double GetWidth() const {return itsWidth;} private: double itsWidth; double itsHeight; }; [...] Imagine that one day the users demand the ability to manipulate squares in addition to rectangles. [...] Clearly, a square is a rectangle for all normal intents and purposes. Since the ISA relationship holds, it is logical to model the Square class as being derived from Rectangle. [...] Square will inherit the SetWidth and SetHeight functions. These functions are utterly inappropriate for a Square, since the width and height of a square are identical. This should be a significant clue that there is a problem with the design. However, there is a way to sidestep the problem. We could override SetWidth and SetHeight [...] But consider the following function: void f(Rectangle& r) { r.SetWidth(32); // calls Rectangle::SetWidth } If we pass a reference to a Square object into this function, the Square object will be corrupted because the height won’t be changed. This is a clear violation of LSP. The function does not work for derivatives of its arguments. [...]
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.
因此,可调整大小的正方形不是可调整大小的矩形。
一些补充:我想知道为什么没有人写基类的不变量、前提条件和后置条件,这些派生类必须遵守。 对于派生类D来说,基类B完全可转换,类D必须服从某些条件:
基类的内变体必须由派生类保留 派生类不能加强基类的先决条件 派生类不能削弱基类的后置条件。
因此派生类必须知道基类施加的上述三个条件。因此,子类型的规则是预先确定的。这意味着,只有当子类型遵守某些规则时,才应该遵守'IS A'关系。这些规则,以不变量、前置条件和后置条件的形式,应该由正式的“设计契约”来决定。
关于这个问题的进一步讨论可以在我的博客:利斯科夫替换原理
利斯科夫替换原理
(固体)
继承子类型化
维基里斯科夫替换原理(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的代码示例并进行了修复。