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
}
blunova
  • 388
  • 1
  • 4
  • 16
user1578653
  • 329
  • 2
  • 7
  • 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
  • Why are you defining the constructor in the base class? The constructor breaks LSP as `Square` throws an exception if the two parameters aren't equal. – David Arno Nov 25 '15 at 13:43
  • @DavidArno I'm starting to feel like I haven't understood this at all...could you explain how you'd solve this? – user1578653 Nov 25 '15 at 13:48
  • I'm unsure which language your example code is written in, but I'm assuming `abstract public function __construct($width,$height);` doesn't need to exist in `AFourSidedShape`. So, sticking with your general approach, I'd get rid of it and change `Square`'s constructor to only accept one parameter, `$width` and would set both `$this->width` and `$this->height` to that value. As to how I'd really solve it, I'd make `AFourSidedShape` an interface and have `Rectangle` and `Square` implement their own versions. Not sure if that's an option for you here though. – David Arno Nov 25 '15 at 13:53
  • With reference to your update, I wasn't very clear with what I meant. The interface should only define `scaleUp` and `scaleDown` as they are the only things common to `Square` and `Rectangle` :) – David Arno Nov 25 '15 at 14:08
  • @DavidArno Thanks for clarifying. So in this instance the advantage of having FourSidedShape as an interface instead of an abstract class is that the only 2 common methods are scaleUp and scaleDown, and they both implement these functions differently. So it's a choice between having an abstract class with all abstract methods or an interface, the latter of which is better. – user1578653 Nov 25 '15 at 14:22
  • That's about it. `scaleUp` and `scaleDown` can be implemented by both `Rectangle` and `Square` without 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:27
  • 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
  • 1
    I would consider the name `Quadrilateral` instead of `AFourSidedShape`. – corsiKa Nov 25 '15 at 17:43
  • 1
    @DocBrown the answers to the other question go into detail explaining _why_ what this question proposes is a bad idea and _why_ it will not work. –  Nov 25 '15 at 18:04
  • 2
    @Snowman: ??? The topmost answer of that other question says exactly the opposite, it says "breaking the inheritance chain between Square and Rectangle is a valid solution" . This is actually what the OP here suggests, but he addtionally introduces a solution with a common base class/interface and asks if that violates the LSP - that case is IMHO not handled in the answers of the other question. – Doc Brown Nov 25 '15 at 19:41
  • 1
    @DocBrown The top answer might not, but e.g. [this one](http://programmers.stackexchange.com/a/238527) does. –  Nov 25 '15 at 19:52
  • 2
    @Snowman: do you really suggest to close a question as dupe because somewhere down below at the fifth place from the top is an answer that *might* contain the information which could be applied to the case? I would really prefer closing question because the *question itself* is clearly a dupe. – Doc Brown Nov 25 '15 at 19:56
  • 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?" –  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 09:29
  • As an aside, a four sided shape has 4 sizes anyway, not only width and height, so if you had actually implemented a four sided shape correctly you would have created two more problems. – Scara95 Dec 03 '15 at 23:21

7 Answers7

22

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.

gsnedders
  • 103
  • 4
Scant Roger
  • 9,038
  • 2
  • 29
  • 47
  • 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
  • *"In this exercise, Square is a subtype of Rectangle."* - ??? That is exactly what the OP **changed** by choosing a common interface. I am discussing the question, not the Wikipedia exercise. – Doc Brown Nov 25 '15 at 16:50
  • @DocBrown I may misunderstand, but my point is that the interface doesn't get you out of trouble. It's just solving a different problem. By abstracting with an interface, you've lost the what makes the Rectangle/Square problem meaningful. It's trivial to define a new contract that doesn't enforce Rectangles post conditions. – Scant Roger Nov 25 '15 at 16:54
  • @ScantRoger: well, the OP asked if his new design still violates the LSP - and I think we both agree "as long as he does not try to make the post conditions generic, it does not". The "meaning of the Rectangle/Square problem" is probably simply to demonstrate a violation of the LSP, that is lost, of course. But the new design is a meaningful design (see the answer of @user61852), and it is a correct way to write programs which deal with Rectangle and Square objects in a generic manner. – Doc Brown Nov 25 '15 at 17:00
  • @DocBrown Right, the design doesn't violate LSP but it also doesn't provide a solution to the "classic rectangle/square problem". I took the title, "Does this code solve the square/rectangle Liskov Substution Principle example?", literally. Answer: nope. – Scant Roger Nov 25 '15 at 17:04
  • @ScantRoger: I think you are interpreting more into the term "classic rectangle/square problem" than I do ;-) – Doc Brown Nov 25 '15 at 17:08
  • @DocBrown `CalcArea` and `GetBoundingBox` have no problem with the LSP, even in the `Square extends Rectangle` case, or the `Rectangle extends Square` case. – user253751 Nov 26 '15 at 09:20
  • @immibis: of course, but modifying methods won't violate the LSP in the OPs solution either. They will only violate the "POLA" (as I explained in my answer) – Doc Brown Nov 26 '15 at 09:52
  • > you could simply design some generic object (no shape or behavior), have everything inherit from the object, and then swap in specific implementations. - Sounds like Java's `Object` class lol. – Christopher Francisco Nov 22 '17 at 19:04
11

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".

Doc Brown
  • 199,015
  • 33
  • 367
  • 565
  • 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](https://en.wikipedia.org/wiki/Liskov_substitution_principle#A_typical_violation). That's solving a different problem. – Scant Roger Nov 25 '15 at 16:50
  • @ScantRoger: see my edit to the question. Did I get you right? – Doc Brown Nov 25 '15 at 17:24
4

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

Programs like Inkscape, Photoshop, The Gimp, Illustrator and so on don't have separate rectangle and square tools in their palettes. 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 "rectangles 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 completely different and incompatible square object when you press shift, only to fall-back to an incompatible rectangle when you release Shift. That could be easily corroborated by downloading the source code of those programs that are open-source, like Inkscape 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 philosophical 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.

lennon310
  • 3,132
  • 6
  • 16
  • 33
Tulains Córdova
  • 39,201
  • 12
  • 97
  • 154
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.

Pieter B
  • 12,867
  • 1
  • 40
  • 65
  • *"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
1

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).

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

You are completely missing the point. If I change the width of a rectangle then the height is unchanged. Your square class breaks that.

As long as you are aware of the LSP violation, feel free to make square a subclass of rect. Change the implementation of setWidth and setHeight to assert(). If you like add a method setWidthAndHeight() which asserts if the result is not a square. Use what you can from the base class.

gnasher729
  • 42,090
  • 4
  • 59
  • 119