我听说利斯科夫替换原则(LSP)是面向对象设计的基本原则。它是什么?它的一些使用例子是什么?
当前回答
以Board数组的形式实现ThreeDBoard会有用吗?
也许你想把不同平面上的ThreeDBoard切片作为一个板。在这种情况下,您可能希望为Board抽象出一个接口(或抽象类),以允许多种实现。
就外部接口而言,您可能希望为TwoDBoard和ThreeDBoard提取一个Board接口(尽管上述方法都不适合)。
其他回答
关于LSP的一个很好的例子(在我最近听到的播客中,Bob叔叔给出了一个例子)是,有时候在自然语言中听起来正确的东西在代码中却不太适用。
在数学中,正方形是长方形。实际上,它是矩形的专门化。“is a”使您想用继承来建模。然而,如果在代码中你从Rectangle派生出Square,那么Square应该可以在任何你想要Rectangle的地方使用。这就导致了一些奇怪的行为。
假设你在你的Rectangle基类上有SetWidth和SetHeight方法;这似乎完全合乎逻辑。然而,如果你的矩形引用指向一个正方形,那么SetWidth和SetHeight没有意义,因为设置一个会改变另一个来匹配它。在这种情况下,Square未能通过矩形的利斯科夫替换测试,并且让Square继承Rectangle的抽象是一个糟糕的抽象。
你们都应该看看其他用励志海报解释的无价的坚实原则。
罗伯特·马丁有一篇关于利斯科夫替换原理的优秀论文。它讨论了可能违反原则的微妙和不那么微妙的方式。
论文的一些相关部分(注意,第二个例子被大量压缩):
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是如何符合的:
<?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 !
}
}
现在子类型不能以同样的方式使用,因为它们不再产生相同的结果。
让我试着考虑一个接口:
interface Planet{
}
这是由类实现的:
class Earth implements Planet {
public $radius;
public function construct($radius) {
$this->radius = $radius;
}
}
你将使用地球作为:
$planet = new Earth(6371);
$calc = new SurfaceAreaCalculator($planet);
$calc->output();
现在再考虑一个扩展到地球的阶级:
class LiveablePlanet extends Earth{
public function color(){
}
}
根据LSP的说法,你应该可以用LiveablePlanet代替Earth,而且它不会破坏你的系统。如:
$planet = new LiveablePlanet(6371); // Earlier we were using Earth here
$calc = new SurfaceAreaCalculator($planet);
$calc->output();
这里的例子
在一个非常简单的句子中,我们可以说:
子类不能违背它的基类特征。它必须有能力。我们可以说这和子类型是一样的。