我听说利斯科夫替换原则(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 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是指同一超类的对象应该能够在不破坏任何东西的情况下相互交换。
例如,如果我们有一个从Animal类派生的Cat和Dog类,那么任何使用Animal类的函数都应该能够使用Cat或Dog,并且行为正常。
我在每个答案中都看到了矩形和正方形,以及如何违反LSP。
我想用一个真实的例子来展示LSP是如何符合的:
<?php
interface Database
{
public function selectQuery(string $sql): array;
}
class SQLiteDatabase implements Database
{
public function selectQuery(string $sql): array
{
// sqlite specific code
return $result;
}
}
class MySQLDatabase implements Database
{
public function selectQuery(string $sql): array
{
// mysql specific code
return $result;
}
}
这种设计符合LSP,因为无论我们选择使用哪种实现,行为都不会改变。
是的,你可以在这个配置中违反LSP,做一个简单的改变,像这样:
<?php
interface Database
{
public function selectQuery(string $sql): array;
}
class SQLiteDatabase implements Database
{
public function selectQuery(string $sql): array
{
// sqlite specific code
return $result;
}
}
class MySQLDatabase implements Database
{
public function selectQuery(string $sql): array
{
// mysql specific code
return ['result' => $result]; // This violates LSP !
}
}
现在子类型不能以同样的方式使用,因为它们不再产生相同的结果。
使用指向基类的指针或引用的函数必须能够在不知道它的情况下使用派生类的对象。
当我第一次阅读LSP时,我认为这是一个非常严格的含义,本质上等同于接口实现和类型安全强制转换。这意味着语言本身要么保证LSP,要么不保证LSP。例如,在严格意义上,ThreeDBoard当然可以取代Board,就编译器而言。
在阅读了更多关于LSP的概念之后,我发现LSP的解释通常比这更广泛。
简而言之,对于客户端代码来说,“知道”指针后面的对象是派生类型而不是指针类型的含义并不仅限于类型安全。对LSP的遵守也可以通过探测对象的实际行为进行测试。也就是说,检查对象的状态和方法参数对方法调用结果或从对象抛出的异常类型的影响。
再次回到示例,理论上Board方法可以在ThreeDBoard上很好地工作。然而,在实践中,在不妨碍ThreeDBoard打算添加的功能的情况下,防止客户端可能无法正确处理的行为差异是非常困难的。
掌握了这些知识后,评估LSP粘附性可以成为一个很好的工具,可以确定何时组合机制更适合扩展现有功能,而不是继承。