6

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
}
  • 2
    You are still carrying width and height around in the square. A square only has a side length and should only have one parameter in the constructor. Similar you have lost the ability for the rectangle to adjust only width or only height. – Bent Nov 25 '15 at 13:21
  • 1
    @Snowman: the only thing that other question has in common with the actual one is that it deals with the Square/Rectangle problem. That obviously does not make it a dupe. – Doc Brown Nov 25 '15 at 16:54
  • 1
    One can always just drop the Square class and implement the IsSquare property/Method on the Rectangle class which will check if the rectangle is, in fact, a square. – T. Sar Nov 25 '15 at 17:11
  • 3
    @DocBrown how many times do we need to rehash the "shape polymorphism" problem? I retracted my close vote anyway, maybe we can use this as a dupe target next time someone says "what if we make squares and rectangles both quadrilaterals instead?" – user22815 Nov 25 '15 at 19:59
  • 3
    The Rectangle/Square becomes non-issue when you cannot set Width/Height in the base type. That is why I believe this is duplicate. So this solution is not because some "FourSidedShape" exists, but because the base class doesn't allow to set Width/Height exactly. – Euphoric Nov 26 '15 at 9:29
21

I'm going to assume you are trying to solve the "Typical Violation" section of the LSP Wikipedia article. If that's the case, you haven't solved it and the section states clearly why. Specifically, start with the setup: "Square class that derives from a Rectangle class, assuming getter and setter methods exist for both width and height." LSP states that the subclass should be able to stand-in for the super class.

But, it's not just that! That's what you and your commenters are missing. If it was just a matter of torturing your design until you could make the swap (everything is a AFourSidedShape), you could simply design some generic object (no shape or behavior), have everything inherit from the object, and then swap in specific implementations. Think about what would happen if you did that. You'd constantly be interrogating the objects to determine what they can do and/or violating their post conditions That's what the LSP hates.

So in your case, a AFourSidedShape doesn't really solve anything because you'd constantly be checking if your specific implementations were Rectangles or Squares to ensure post conditions are correct -- a Square's width can magically change when you update its height but not a Rectangle's. That's what the sections means when it says, "these methods will weaken (violate) the postconditions for the Rectangle setters." You can't get away from less-than-ideal behavior. It's a tradeoff. (Unless you make your objects immutable! Hooray for immutability!)

Don't feel bad. The Rectangle/Square with getters/setters for width/height problem is meant to be unsolvable in the trivial sense. It's a great, simple example of why LSP is difficult in practice.

  • 1
    I do not think your answer is correct. "because you'd constantly be checking if your specific implementations were Rectangles or Squares" - why? I can imagine methods like "CalcArea" or "GetBoundingBox" implemented in terms of AFourSidedShape, with no necessity of type checking. That would actually make some sense. – Doc Brown Nov 25 '15 at 15:50
  • 3
    @DocBrown ah, sorry I'm not clear. This constraint, "assuming getter and setter methods..." is the key. The generic functions of AFourSidedShape are fine, it's those setters that ruin our day. If you generically call a setter on a Square, you get a crappy side effect -- the width or height magically change when you set the height or width! That violates the post condition of the Rectangle, so they have different rules. They only way we can test if the behavior is correct is to know if we're dealing with a Rectangle or Square. It's the classic is-a relationship that isn't quite is-a. – Scant Roger Nov 25 '15 at 16:19
  • 1
    Hooray for immutability. Immutability makes Haskell hard, but learning Haskell makes immutability easier and immutability is useful everywhere. I also love Haskell's inheritance model with a strict separation of typeclasses (~ interfaces) and concrete types (~ final classes) with no "instantiable but still extensible" weird middle ground. – John Dvorak Nov 25 '15 at 16:27
  • I don't think so. You are assuming there must be common post conditions for rectangles and squares, which is not necessary when neither of it is the base class of the other. A square can have post conditions, a rectangle too, and the common subset of them might be valid for the common interface/base class. – Doc Brown Nov 25 '15 at 16:33
  • @DocBrown I'm saying that it's impossible to make their post conditions generic. In this exercise, Square is a subtype of Rectangle. Right? I thought that was the whole point. Square is-a Rectangle but it doesn't just define new properties with post conditions, it alters the post conditions of its parent. That's why the Wikipedia article calls it out. If it was solvable by throwing an interface at it, it wouldn't be interesting. It's interesting because there's no trivial solution. – Scant Roger Nov 25 '15 at 16:40
10

The LSP is about the contract of a class, and that inherited classes must still fulfill the same contract as their base class. An interface just in code will typically only define parts of that contract, mainly the syntactical part, and the semantics might be partially given by descriptive names of the methods or the parameters. Other parts of the contract are often just defined in comments, by adding "assertion statements", by making use of specific language features, or they might be encoded into unit tests.

Thus if you solved the LSP violation really, depends on the full semantic contract of your interface. If the contract looks like this (which is IMHO the more obvious behaviour):

// contract: a four sided shape is an object with two individual, independent
// properties "width" and "height"
interface AFourSidedShape{
    public function setWidth($width);
    public function setHeight($height);
    public function getWidth();
    public function getHeight();
}

then Square class does not fulfill the same contract as its base class, so it still violates the LSP. One could write that more formally in terms of "post conditions", checking that each time you call setHeight, the value of getWidth does not change, and vice versa.

If, however, your semantic contract looks like this:

 // contract: a four sided shape is an object with two 
 // properties "width" and "height" which must not 
 // be assumed to be independent; maybe changing one can change the other
 interface AFourSidedShape{
     // ...
 }

then there is no LSP violation any more. From your question and the way you describe the constructor constraints, I guess that is the contract you have in mind. However, the latter might violate the principle of least astonishment, a "setter" which for some objects changes another value, and for others it does not, can be mind-boggling to the average user of your class. Better avoid to put such setters in the common interface, LSP obeyed or not.

Side note: of course, the original Square/Rectangle problem can be understood as "is there an implementation where Square derives from Rectangle or vice versa directly which does not violate the LSP". If one reads it this way, then @ScantRoger is correct and your design solves a different problem. However, I read the problem quite differently as is there a way to incorporate inheritance to deal with rectangles and squares in a generic manner without violating LSP, and for this problem the answer is "yes, your suggestion will solve that problem".

  • This is correct regarding a code contract, but doesn't address pre/post conditions. (Non-code contracts) So, if my expectation is that a Rectangle's width and height can be adjusted independently and a Square's width changes with height and height with width, we have different post conditions. There's no way to make them generic. AFourSidedShape only enforces the code contract. As stated in the Wikipedia article, that's why Rectangle->Shape is not trivially solvable. Those darn setters behave differently so we have to know what we're dealing with. – Scant Roger Nov 25 '15 at 16:24
  • @ScantRoger: the LSP does not state "any postconditions of of inherited classes must be valid also for a base class/interface", so what's your point? – Doc Brown Nov 25 '15 at 16:37
  • 1
    From the article: "objects of type S may substitute objects of type T ... without altering any of the desirable properties of that program (correctness, task performed, etc.)." If type T's correct behavior is defined by a post condition, you cannot substitute T with type S if it doesn't share the behavior. Otherwise, the substitution has altered the "desirable properties" of the program. – Scant Roger Nov 25 '15 at 16:44
  • @ScantRoger: yes, If type T's correct behavior is defined by a post condition, then changing that in a subtype would violate the LSP. I am just saying "as long as you do not define any post condition for AFourSidedShape, you do not violate the LSP: – Doc Brown Nov 25 '15 at 16:47
  • 1
    But that's not the exercise. That's solving a different problem. – Scant Roger Nov 25 '15 at 16:50
5

In a practical way, that has been solved many times.

Programs like Inscake, Photoshop, The Gimp, Illustrator and so on don't have separate rectangle and square tools in their palletes. They have only one tool for drawing rectangles and squares. And they don't call it "quadrilateral", they call it rectangle. Some even call it "ractangles and squares tool" so confused users wouldn't migrate to other apps that would allow them to draw squares.

So it seems to me that they found a way for it to work for them. And I suspect what they have done is assume a square is just a rectangle with equal height and width. They even allow you, by using the shift key when drawing, to lock it in a way that you can create a square.

I don't really think those programs switch from a rectangle object to a completelly different and incompatible square object when you press shift, only to fall-back to an incompatible rectangle when you release the shift-key. That could be easyly corroborated by downloading the source code of those programs that are open-source, like Inskscape or The Gimp.

So I think that when you changed the design from inheritance of a superclass to implementation of an interface, you "kinda" solved it.

Because in the case of inheritance you are stating that a square is a rectangle, which is false. In the other hand when you go with implementing an interface you are saying that a square can act as a rectangle. In that scenario what the methods do or don't do under the hood is an implementation detail. Better yet if you forget about squares altogether you are fine because nothing prevents you from creating a rectangle with equal sides.

So it's really a phylosophical problem and not a practical one.

In Wikipedia definition

A square...It can also be defined as a rectangle in 
which two adjacent sides have equal length.
1

With the LSP and inheritance basically means: the child class can do everything that the parent class can and a bit more.

And here comes the problem:

  • For a square : you can fold a square into a half-square triangle. Which mean you could make a method: "makesquaretriangle". This is not true for all rectangles.

  • For a rectangle : If you multiply the height by a factor, and the width by the inverse of that factor, it will still have the same surface area. Which means you could make a method: "doublemyheightbutkeepsurface" which doubles the height and automatically halves the width.
    This is not true for a square.

So you see, both square and rectangle have methods that don't make sense on the other, that's why they can't always be substituted. And why they can't inherit from each other either way.

The LSP is specifically for the cases where you want to either inherit A from B or want to inherit B from A, but can't. Other cases where you inherit both B and A from a common base class are out of the scope of the LSP. So your solution has nothing to do with the LSP.

  • "And why they can't inherit from each other either way." - you missed the fact the OP proposes a solution where neither Squre inherits from Rectangle, nor Rectangle from Square. – Doc Brown Nov 25 '15 at 15:46
  • @DocBrown added a paragraph. – Pieter B Nov 25 '15 at 17:21
  • What you added is not correct, the scope of LSP is any case where inheritance is involved. When two classs A and B have a common base class C as in the OPs question, one can still design this either "right" or "wrong" (violating the LSP or not). And as far as I understand the question, the OP did it "right". – Doc Brown Nov 25 '15 at 17:28
  • First you have a case with A and B and run into troubles. Now you introduce C and solve it for A and C and solve it for B and C, it's still not resolved for A and B, that's just evading the problem. You could just as well make a class C : "nothing" and inherit A and B from that. – Pieter B Nov 25 '15 at 18:41
  • counterexample: first, you have A=Squares and B=Rectangles and want to deal with them in a generic manner. Deriving A from B or B from A does neither work well because you will violate the LSP both ways. But introducing a common base C class for both can actually solve that problem, as shown in the question, as long as A and B both obey to the LSP in relation to C. Heck, this is not even artificial, this could be actually a real-world example from a Gfx library. – Doc Brown Nov 25 '15 at 19:31
0

AFoursidedShape hides information about the dependencies between height and width, so LSP is not violated as is the case with Square inhereting from Rectangle.

The class rectangle and square are representations of squares and rectangles, therefor they do not necessary share the same relationship as what they represent. In other words a mathemtical Square has a relationship with mathemtical rectangle, but that does not imply the class square must necesseraly have same relationship with class rectangle. If we still want to treat Square as a Rectangle another way of doing it without violating LSP could be to have an interface thats called ARectangleReader which only has contract for the getters getHeight and getWidth which is implemented by the Square class and Rectangle class.

Another way to think about it: does a mutable Square really behave like a mutable Rectangle?

0

I see that is an old question but I want to give my 2 cents.

You didn't slove the problem and your design doesn't respect LSP, because one user could have this code :

AFourSidedShape square = new Square(5, 5);
... //Later in code.
shape.setWidth(4);
shape.setHeight(3);
... //And later.
shape.getHeight() * shape.getWidth() // The user would expect its area is 12 but its currently 9.

First: try to manipulate shapes in the most general way as you can, squares are rectangles, so manipulate squares as rectangles and let other code handle the rest, for example, if you have a paint program it would be good to let users create squares, rectangles when a user creates a squares using the GUI, in reality, it creates a rectangle which has the same width and height and then let the UI shows it, for resize operations, you could also offer the ability to grow and maintain the width/height by a percentage, the controller of this action would apply the general formula and it will works.

Second: You should trust in objects, that's what encapsulation is, Try to put common properties on the interface for example :

interface Shape { //This interface represents all the 2D shapes.
    double getArea();
    double getPerimeter();
}

class Rectangle implements Shape {
    private width;
    private height;
    ...
    public double getArea() { return width * height; }
    public double getPerimeter() { return width * 2 + height * 2; }
}

and then implements that methods on the specified classes, by doing that, you are encapsulating the area and perimeter calculation inside the class, this is definitely better than calculating these properties outside the shape object, because it reveals that you trust in what object will give you, but when you do it outside the object, it seems as you don't trust in that object and you are assigning this responsibility to another object (which in fact you trust in it).

protected by gnat Apr 12 '16 at 19:35

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

Not the answer you're looking for? Browse other questions tagged or ask your own question.