我听说利斯科夫替换原则(LSP)是面向对象设计的基本原则。它是什么?它的一些使用例子是什么?
当前回答
LSP的这种形式太强大了:
如果对于每个类型为S的对象o1,都有一个类型为T的对象o2,使得对于所有用T定义的程序P,当o1取代o2时,P的行为不变,那么S是T的子类型。
这基本上意味着S是t的另一个完全封装的实现,我可以大胆地认为性能是P行为的一部分……
因此,基本上,任何延迟绑定的使用都违反了LSP。当我们用一种类型的对象替换另一种类型的对象时,获得不同的行为是OO的全部意义所在!
维基百科引用的公式更好,因为属性取决于上下文,并不一定包括程序的整个行为。
其他回答
假设我们在代码中使用了一个矩形
r = new Rectangle();
// ...
r.setDimensions(1,2);
r.fill(colors.red());
canvas.draw(r);
在几何课上,我们学过正方形是一种特殊类型的矩形,因为它的长宽相等。让我们根据下面的信息创建一个Square类:
class Square extends Rectangle {
setDimensions(width, height){
assert(width == height);
super.setDimensions(width, height);
}
}
如果我们在第一个代码中将矩形替换为正方形,那么它将会中断:
r = new Square();
// ...
r.setDimensions(1,2); // assertion width == height failed
r.fill(colors.red());
canvas.draw(r);
这是因为正方形有一个我们在矩形类中没有的新前提条件:width == height。根据LSP,矩形实例应该被矩形子类实例替代。这是因为这些实例通过了矩形实例的类型检查,因此它们将在代码中导致意外错误。
这是wiki文章中“在子类型中不能加强先决条件”部分的一个例子。因此,总而言之,违反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)
子类型中方法参数类型的逆变性。子类型中方法返回类型的协方差。 子类型中的方法不能引发新的异常,除非它们是超类型的方法引发的异常的子类型。
[方差,协方差,逆变,不变性]
罗伯特·马丁有一篇关于利斯科夫替换原理的优秀论文。它讨论了可能违反原则的微妙和不那么微妙的方式。
论文的一些相关部分(注意,第二个例子被大量压缩):
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. [...]
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)。
你可以在那里找到一个解释,什么是利斯科夫替换原则,一般线索帮助你猜测你是否已经违反了它,一个方法的例子,将帮助你使你的类层次结构更安全。
推荐文章
- getter和setter是糟糕的设计吗?相互矛盾的建议
- 你如何设计面向对象的项目?
- 克隆对象没有引用javascript
- PHP中接口的意义是什么?
- 虚拟方法和抽象方法的区别
- 合并两个PHP对象的最佳方法是什么?
- 继承和组合的区别
- 如何在PostgreSQL中查看视图的CREATE VIEW代码?
- 打印Python类的所有属性
- 面向对象编程,函数式编程,过程式编程
- 面向对象的Javascript最佳实践?
- 为什么我更喜欢使用成员初始化列表?
- 如何让PHP类构造函数调用父类的父类构造函数?
- 理解__getattr__和__getattribute__之间的区别
- 让setter返回"this"是不好的做法吗?