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
}
Squarethrows an exception if the two parameters aren't equal. – David Arno Nov 25 '15 at 13:43abstract public function __construct($width,$height);doesn't need to exist inAFourSidedShape. So, sticking with your general approach, I'd get rid of it and changeSquare's constructor to only accept one parameter,$widthand would set both$this->widthand$this->heightto that value. As to how I'd really solve it, I'd makeAFourSidedShapean interface and haveRectangleandSquareimplement their own versions. Not sure if that's an option for you here though. – David Arno Nov 25 '15 at 13:53scaleUpandscaleDownas they are the only things common toSquareandRectangle:) – David Arno Nov 25 '15 at 14:08scaleUpandscaleDowncan be implemented by bothRectangleandSquarewithout causing LSP problems as you correctly surmised at the beginning. The moment you put any references to width or height into a base class though, you are heading off down the Rectangle/Square LSP slippery slope. – David Arno Nov 25 '15 at 15:27Quadrilateralinstead ofAFourSidedShape. – corsiKa Nov 25 '15 at 17:43