I just wanted to check that I understand the LSP correctly and can solve it. I am taking the classic rectangle/square problem and attempting a solution:
class Rectangle{
public $width;
public $height;
function setWidth($width){
$this->width = $width;
}
function setHeight($height){
$this->height = $height;
}
}
class Square extends Rectangle{
function setWidth($width){
$this->width = $width;
$this->height = $width;
}
function setHeight($height){
$this->height = $height;
$this->width = $height;
}
}
If you had some code like:
function changeSize(Rectangle $rect){
$rect->setWidth(10);
$rect->setHeight(30);
$this->assertEquals(10,$rect->width);
$this->assertEquals(30,$rect->height);
}
Then obviously rectangles and squares are not interchangeable, as square introduces a constraint to the parent class. Therefore, a square should not inherit from rectangles.
But surely we can agree that both square and rectangle are four sided shapes? This is my proposed solution, based on this premise:
abstract class AFourSidedShape{
public $width;
public $height;
abstract public function __construct($width,$height);
public function scaleUp($percentage){
$this->height = $this->height + (($this->height / 100) * $percentage);
$this->width = $this->width + (($this->width / 100) * $percentage);
}
public function scaleDown($percentage){
$this->height = $this->height - (($this->height / 100) * $percentage);
$this->width = $this->width - (($this->width / 100) * $percentage);
}
}
class Rectangle extends AFourSidedShape{
function __construct($width, $height){
$this->width = $width;
$this->height = $height;
}
}
class Square extends AFourSidedShape{
function __construct($width, $height){
if($width != $height){
throw new InvalidArgumentException('Sides must be equal');
}else{
$this->width = $width;
$this->height = $height;
}
}
}
Our client code should be changed to something like:
function changeSize(AFourSidedShape $shape){
$origWidth = $shape->width;
$origHeight = $shape->height;
$shape->scaleUp(10);
$this->assertEquals($origWidth + (($origWidth/100) * 10),$shape->width);
$this->assertEquals($origHeight + (($origHeight/100) * 10),$shape->height);
}
My theory is: rectangles and squares really are both foursidedshapes, so there shouldn't be a problem with inheriting from the foursidedshape abstract class. Whilst the square is still adding extra constraints in the constructor (i.e. throwing an error if the sides aren't equal), it shouldn't be a problem since we haven't implemented the constructor in the abstract parent class, and so client code shouldn't make assumptions about what you can/cannot pass into it anyway.
My question is: have I understood LSP, and does this new design solve the LSP problem for square/rectangle?
When using interfaces as suggested:
interface AFourSidedShape{
public function setWidth($width);
public function setHeight($height);
public function getWidth();
public function getHeight();
}
class Rectangle implements AFourSidedShape{
private $width;
private $height;
public function __construct($width,$height){
$this->width = $width;
$this->height = $height;
}
public function setWidth($width){
$this->width = $width;
}
public function setHeight($height){
$this->height = $height;
}
//getwidth, getheight
}
class Square implements AFourSidedShape{
private $width;
private $height;
public function __construct($sideLength){
$this->width = $sideLength;
$this->height = $sideLength;
}
public function setWidth($width){
$this->width = $width;
$this->height = $width;
}
public function setHeight($height){
$this->height = $height;
$this->width = $height;
}
//getwidth, getheight
}