关于设计的经典例子,SO上有几个线程打破了Liskov替换原则:Square和Rectangle类。问题首先证明如果Square扩展Rectangle它会违反LSP。很多人问如何解决这个问题。我已经听过一些想法,包括有一个由Rectangle和Square等扩展的Polygon基类。我也想过这个,对我来说这是错误的方向 - 错误的讨论。 Square不会扩展Rectangle。正方形不会为矩形添加功能或属性。正方形是一个矩形。它是一种矩形。
鉴于存在一个Rectangle类,并且我的任务是构建一个重用Rectangle的Square类,我将构建一个带有受保护属性的Square类,该属性是Rectangle的一个实例。我将公开Rectangle的所有属性/方法,这些属性/方法也适用于Square,而不会暴露那些不适用的方块。 Square不适用于Rectangle的任何属性或方法都会出现在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扩展矩形的情况下破坏LSP的原因是有问题的是,有人在Rectangle中添加一个方法,通过某种因素改变Rectangle的区域 - 让我们调用一个方法带有像
这样的签名public function transformArea (factor)
实现为将宽度乘以因子。该论点指出,现在你的方块将增加因子的平方,因为你的宽度与高度相同,如果宽度增加10%,则方块增长21%。这不完全正确,因为这不是这些系统实际工作的方式。实际发生的是宽度增加因子,矩形的面积也增长因子,但现在Square的实例不再是正方形(在Square延伸矩形的情况下),因为宽度和高度不再相同。在我的版本中,其他人可以将该方法添加到Rectangle并且它不会破坏Square - 没有人可以在Square的实例上使用Rectangle的transformArea方法,因为它尚未被Square类公开。
如果有人要求添加此方法,然后另一个程序员通过简单地将它从Rectangle类中公开,将它添加到Square类中:
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保持正方形(width == height)。我不同意。 Square不公开改变的方法,也不公开两者。 Rectangle有一些方法可以打破,但没有暴露。
就我个人而言,这更适合现实世界。 Square是一个矩形。所有正方形都是矩形,但并非所有矩形都是正方形。
我的解决方案是否违反任何SOLID规则?
答案 0 :(得分:1)
设计完全取决于权衡,“最佳”解决方案因应用而异:
Square
is-a Rectangle
问题的传统解决方案是在保持is-a关系的同时使类不可变(权衡)。Square
has-a Rectangle
)这是一个完全有效的解决方案,但现在您放弃了is-a关系,同时保持对象可变。您无法再将Square
实例传递给采用Rectangle
。您可以选择其中一个,但如果您尝试同时执行这两个操作,则最终会破坏LSP。
PS:您的Square
类不应该有isSquare
函数,类的责任是确保实例始终处于有效状态。