我听说利斯科夫替换原则(LSP)是面向对象设计的基本原则。它是什么?它的一些使用例子是什么?
当前回答
我在每个答案中都看到了矩形和正方形,以及如何违反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在技术上是什么:你基本上希望能够从子类型细节中抽象出来,并安全地使用超类型。
所以利斯科夫有3条基本规则:
Signature Rule : There should be a valid implementation of every operation of the supertype in the subtype syntactically. Something a compiler will be able to check for you. There is a little rule about throwing fewer exceptions and being at least as accessible as the supertype methods. Methods Rule: The implementation of those operations is semantically sound. Weaker Preconditions : The subtype functions should take at least what the supertype took as input, if not more. Stronger Postconditions: They should produce a subset of the output the supertype methods produced. Properties Rule : This goes beyond individual function calls. Invariants : Things that are always true must remain true. Eg. a Set's size is never negative. Evolutionary Properties : Usually something to do with immutability or the kind of states the object can be in. Or maybe the object only grows and never shrinks so the subtype methods shouldn't make it.
所有这些属性都需要保留,并且额外的子类型功能不应该违反超类型属性。
如果这三件事都处理好了,那么您就从底层的东西中抽象出来了,并且您正在编写松散耦合的代码。
来源:程序开发在Java -芭芭拉利斯科夫
利斯科夫替换原则(来自Mark Seemann的书)指出,我们应该能够在不破坏客户端或实现的情况下,用另一个接口的实现替换一个接口的实现。正是这一原则使我们能够解决未来出现的需求,即使我们今天不能预见它们。
If we unplug the computer from the wall (Implementation), neither the wall outlet (Interface) nor the computer (Client) breaks down (in fact, if it’s a laptop computer, it can even run on its batteries for a period of time). With software, however, a client often expects a service to be available. If the service was removed, we get a NullReferenceException. To deal with this type of situation, we can create an implementation of an interface that does “nothing.” This is a design pattern known as Null Object,[4] and it corresponds roughly to unplugging the computer from the wall. Because we’re using loose coupling, we can replace a real implementation with something that does nothing without causing trouble.
我建议您阅读这篇文章:违反利斯科夫替换原则(LSP)。
你可以在那里找到一个解释,什么是利斯科夫替换原则,一般线索帮助你猜测你是否已经违反了它,一个方法的例子,将帮助你使你的类层次结构更安全。
以Board数组的形式实现ThreeDBoard会有用吗?
也许你想把不同平面上的ThreeDBoard切片作为一个板。在这种情况下,您可能希望为Board抽象出一个接口(或抽象类),以允许多种实现。
就外部接口而言,您可能希望为TwoDBoard和ThreeDBoard提取一个Board接口(尽管上述方法都不适合)。
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.
因此,可调整大小的正方形不是可调整大小的矩形。