【发布时间】:2016-11-07 06:15:50
【问题描述】:
关于 SO 有几个线程与打破 Liskov 替换原则的经典设计示例有关:Square 和 Rectangle 类。问题首先证明如果 Square 扩展了 Rectangle 它违反了 LSP。很多人问如何解决这个问题。我听说过一些想法,包括拥有一个由 Rectangle 和 Square 等扩展的 Polygon 基类。我也想过这个问题,对我来说这是错误的方向 - 错误的讨论。 Square 不扩展 Rectangle。正方形不会向矩形添加功能或属性。正方形是长方形。它是一种矩形。
鉴于存在一个 Rectangle 类,并且我的任务是构建一个重用 Rectangle 的 Square 类,我将构建一个 Square 类,它的受保护属性是 Rectangle 的一个实例。我将公开所有适用于 Square 的 Rectangle 属性/方法,而不公开那些不适用的属性/方法。任何不适用于 Rectangle 的 Square 属性或方法都会出现在我的 Square 类中。这是我在 PHP 中的解决方案:
class Square {
protected $rectangle; // instance of Rectangle
function __construct ($length) {
if (!is_numeric ($length))
throw new Exception ("Square::__contruct - length must be numeric");
$this->rectangle = new Rectangle ($length, $length); // given Rectangle (width, height)
}
public function getArea () {
return $this->rectangle->getArea();
}
public function getLength () {
return $this->rectangle->getWidth(); // or height pick one
}
public function setLength ($length) {
if (!is_numeric ($length))
throw new Exception ("Square::setLength - length must be numeric");
$this->rectangle->setWidth ($length);
$this->rectangle->setHeight ($length);
}
public function isSquare() {
return ($this->rectangle->getHeight() == $this->rectangle->getWidth());
}
}
正如论点所说,在 Square extends Rectangle 的情况下破坏 LSP 存在问题的原因是,其他人向 Rectangle 添加了一个方法,该方法通过某种因素改变了 Rectangle 的区域 - 我们称其为具有类似签名的方法
public function transformArea (factor)
实现为将宽度乘以因子。该论点指出,现在您的正方形将按因子的平方增长,因为您的宽度与高度相同,如果宽度增长 10%,则正方形将增长 21%。这并不完全正确,因为这不是这些系统实际工作的方式。实际发生的是宽度按因子增长,矩形的面积也按因子增长,但现在 Square 的实例实际上不再是正方形(在 Square extends Rectangle 的情况下),因为宽度和高度不再相同。在我的版本中,其他人可以将该方法添加到 Rectangle 并且它不会破坏 Square - 没有人可以在 Square 的实例上使用 Rectangle 的 transformArea 方法,因为它还没有被 Square 类公开。
如果有人指示添加此方法,然后另一个程序员将其添加到 Square 类中,只需将其从 Rectangle 类中公开,如下所示:
public function transformArea ($factor) {
$this->rectangle->transformArea ($factor);
}
然后假设这个人完成了他们的工作并在类上运行了一个单元测试,他们会发现他们只是在运行这个方法时破坏了 isSquare。但是该测试可能会被添加到一堆测试的末尾,并且可能不会揭示错误。但这通常是开发的一个问题,不一定是这个系统的设计缺陷。
正确的答案是通过将长度增加因子的平方根来实现 Square 中的方法。像这样:
public function transformArea ($factor) {
if (!is_numeric ($factor))
throw new Exception ("Square::transformArea - factor must be numeric");
if ($factor <= 0)
throw new Exception ("Square::transformArea - factor must be greater than zero");
$growBy = (float) sqrt ($factor);
$newLength = $this->getLength() * $growBy;
$this->setLength ($newLength);
}
我可以想象的另一个论点是,这个类不能保证它里面的 Rectangle 仍然是一个正方形(宽度 == 高度)。我不同意。 Square 不公开改变其中一个而不是两者的方法。 Rectangle 有可能破坏它的方法,但没有暴露。
就我个人而言,这更适合我的现实世界。正方形是长方形。所有正方形都是矩形,但并非所有矩形都是正方形。
我的解决方案是否违反任何 SOLID 规则?
【问题讨论】:
-
Square/Rectangle 的问题不仅仅是重用或制作新签名。从数学上讲,Square 是 Rectangle 的一种,但是当你编写它时,Square 不能用作矩形,因为它会限制他作为 Rectangle 的使用。
标签: php oop design-patterns solid-principles