0

I want to show an example with Rectangles and Squares:

    class Rectangle {

private int width;
private int height;

public int getWidth() {
    return width;
}

public void setWidth(int width) {
    this.width = width;
}

public int getHeight() {
    return height;
}

public void setHeight(int height) {
    this.height = height;
}

public int area() {
    return width * height;
}

}

class Square extends Rectangle{

@Override
public void setWidth(int width){
    super.setWidth(width);
    super.setHeight(width);
}

@Override
public void setHeight(int height){
    super.setHeight(height);
    super.setWidth(height);
}

}

public class Use {

public static void main(String[] args) {
    Rectangle sq = new Square();
    LSPTest(sq);
}

public static void LSPTest(Rectangle rec) {
    rec.setWidth(5);
    rec.setHeight(4);

    if (rec.area() == 20) {
        // do Something
    }
}

}

If I substitute an instance of Square instead of Rectangle in method LSPTest the behaviour of my programm will be changed. This is contrary to the LSP.

I heard, that immutable object allow to solve this problem. But why?

I changed example. I add constructor in Rectangle:

public Rectangle(int width, int height) {
this.width = width;
this.height = height;
}

Then, I changed setters:

    public Rectangle setWidth(int width) {
    return new Rectangle(width, this.height);
}

public Rectangle setHeight(int height) {
    return new Rectangle(this.width, height);
}

Now, Square looks like:

class Square{
public Square() {

}

public Square(int width, int height) {
    super(width, height);
}

@Override
public Rectangle setWidth(int width) {
    return new Rectangle(width, width);
}

@Override
public Rectangle setHeight(int height) {
    return new Rectangle(height, height);
}

}

And here is the client code:

 public class Use {

public static void main(String[] args) {
    Rectangle sq = new Square(4, 4);
    LSPTest(sq);
}

public static void LSPTest(Rectangle rec) {
    rec = rec.setHeight(5);

    if (rec.area() == 20) {
        System.out.println("yes");
    }
}

}

The same problems remain. What's the difference whether the object itself is changed or a new object is returned. The program still behaves differently for the base class and subclass.

  • 2
    what makes you think immutable objects inherently satisty the LSP? (hint, they don't). – jtahlborn Apr 02 '16 at 21:32
  • I agree with @jtahlborn. And trying to implement an immutable object with a setter that returns a new object seems pretty confusing to me. – Frank Puffer Apr 02 '16 at 21:39
  • 1
    Your Square immutable implementation is the problem here. It shouldn't take two arguments in its constructor, but just one. And it shouldn't override setWidth and setHeight. The base class setWidth() contract is to return a Rectangle with the same height as the original one and the given width. There is no reason to override that in Square: the base method is correct for a Square too: it just doesn't return a Square. – JB Nizet Apr 02 '16 at 22:58

2 Answers2

2

From here I grabbed these quotes (emphasis mine):

Imagine you had SetWidth and SetHeight methods on your Rectangle base class; this seems perfectly logical. However if your Rectangle reference pointed to a Square, then SetWidth and SetHeight doesn't make sense because setting one would change the other to match it. In this case Square fails the Liskov Substitution Test with Rectangle and the abstraction of having Square inherit from Rectangle is a bad one.

...and...

What the LSP indicates is that subtype behavior should match base type behavior as defined in the base type specification. If the rectangle base type spec says that height and width can be set independently, then LSP says that square cannot be a subtype of rectangle. If the rectangle spec says that a rectangle is immutable, then a square can be a subtype of rectangle. It's all about subtypes maintaining the behavior specified for the base type.

I guess it would work if you had a constructor like:

Square(int side){
    super(side,side);
    ...
}

Because there is no way to change something immutable, there are no setters. The square will always be square.

But it should be possible to have a relationship between the two that doesn't violate LSP that also doesn't force you to use immutable objects. We're just going about it wrong.

In mathematics, a square can be considered a type of rectangle. It is, in fact, a more specific type of rectangle. Naïvely, it might seem logical to do Square extends Rectangle, because rectangles just seem so super. But the point of having a subclass is not to created a weaker version of an existing class, rather it should enhance the functionality.

Why not have something like:

class Square{
   void setSides(int side);
   Boundary getSides();
}
class Rectangle extends Square{
    //Overload
    void setSides(int width, int height);
    @Override
    Boundary getSides();
}

I would also like to point out that Setters are for setting. The code below is awful, because you have essentially created a method that will not do what it says it will.

 public Rectangle setWidth(int width) {
    return new Rectangle(width, this.height);
 }
Community
  • 1
  • 1
Laurel
  • 5,965
  • 14
  • 31
  • 57
0

The issue is with the contracts, i.e. the expectations of the programmers that use your Rectangle.

The contract is that you can do a setWidth(15) and afterwards getWidth() will return 15 until you do another setWidth with a different value.
From the perspective of setHeight, this means it must not change height. Continue that line of thought, and the contract for a setter is "update this property to the parameter value and leave all other properties unchanged".

Now in Square, the new invariant getWidth() == getHeight() forces setWidth to also set the height, and voilà: the contract of setHeight is violated.

Of course you could explicitly state in Rectangle.setWidth()'s contract (i.e. the method documentation) that the width may change if setHeight() is called.
But now your contract on setWidth is pretty useless: it will set the width, which may or may not stay constant after a call to setHeight, depending on whatever a subclass may decide to do.

Things can get worse. Assume you roll out your Rectangle, people complain a bit about the unusual contract on the setters but otherwise everything is fine.
But now somebody comes and wants to add a new subclass, OriginCenteredRectangle. Now changing the width also needs to update x and y. Have fun explaining your users that you had to modify the contract of the base class so that another subclass (that only 10% of your users need) could be added.

Practice has shown that the OriginCenteredRectangle problem is far more common than the silliness of that example indicates.
Also, practice has shown that programmers typically are not aware of the full contract, and start writing updateable subclasses that fly in the face of expectations, causing subtle bugs.
So most programming language community finally decides that you need value classes; from what I have seen in C++ and Java, this process takes a decade or two.

Now with immutable classes, your setters suddenly look different: void setWidth(width) becomes Rectangle setWidth(width). I.e. you don't write setters, you write functions that return a new object with a different width.
And that's perfectly acceptable in Square: setWidth remains unchanged and still returns a Rectangle as it must. Of course you will want a function to get back a different square, so Square adds the function Square Square.setSize(size).

You still want a MutableRectangle class just to construct Rectangles without having to create a new copy - you'll have Rectangle toRectangle() which does the constructor call. (The other name for MutableRectangle would be RectangleBuilder, which describes a somewhat more limited recommended use of the class. Pick your style - personally, I think MutableRectangle is fine, it is just that most subclassing attempts will fail so I'd consider making it final.)

toolforger
  • 754
  • 7
  • 22