What's Wrong with the Square and Rectangle Inheritance

What's wrong with the Square and Rectangle inheritance?

I'm not always keen on Liskov since it seems to limit what you can do with inheritance based on behaviour rather than "essence". In my view, inheritance was always meant to be an "is a" relationship, not an "acts exactly like" one.

Having said that, the wikipedia article goes into detail as to why this is considered bad by some, using your exact example:

A typical example that violates LSP is a Square class that derives from a Rectangle class, assuming getter and setter methods exist for both width and height.

The Square class always assumes that the width is equal with the height. If a Square object is used in a context where a Rectangle is expected, unexpected behavior may occur because the dimensions of a Square cannot (or rather should not) be modified independently.

This problem cannot be easily fixed: if we can modify the setter methods in the Square class so that they preserve the Square invariant (i.e., keep the dimensions equal), then these methods will weaken (violate) the postconditions for the Rectangle setters, which state that dimensions can be modified independently.

So, looking at your code alongside the equivalent Rectangle code:

s = Square.new(100)            r = Rectangle.new(100,100)
s.width = 50 r.width = 50
puts s.height puts r.height

the output would be 50 on the left and 100 on the right.

But, this is the important bit from the article, in my view:

Violations of LSP, like this one, may or may not be a problem in practice, depending on the postconditions or invariants that are actually expected by the code that uses classes violating LSP.

In other words, provided the code using the classes understands the behaviour, there is no issue.

Bottom line, a square is a proper subset of a rectangle, for a loose-enough definition of rectangle :-)

Square and Rectangle Inheritance

This assumes Rectangle declares a print() method.

This action

Rectangle c = (Rectangle) b;

doesn't do anything to the instance referenced by b. It only widens the reference variable b to its super type Rectangle.

Invoking print() will have polymorphic behavior and, at runtime, the implementation of Square will be used since c is referencing a Square object, which has set size to 6.

Square b = new Square(6.0);
...
private double side;

public Square(double side) {
super(side, side);
this.side = side;
}

Note that this is the expected behavior in Java since all methods are virtual by default. In other languages like C++ and C#, your cast would have worked since print method in Rectangle isn't declared as virtual.

More info:

  • Java inheritance vs. C# inheritance

Having trouble with Inheritance between Rectangle and Square

The Rectangle::Rectangle() parameter-less constructor should not be declared (in my interpretation of the spec).

Square::Square() with no parameters should not have been defined (in my interpretation of the spec).

The Square::Square(double) constructor should not call the base class's default constructor (which your code currently does). Instead the requirements ask for

Square::Square(double length) : Rectangle(length, length) {}

Hope that helps.

Inheritance concerns between Square/Rectangle (with constrained invariants) which share Quadrilateral base

It depends on what you want the "inheritance" relationship to represent here. Often, what one wants to express is best NOT expressed using what is called "object-oriented inheritance" (indeed, I have found few uses for this kind of inheritance).

In this case, the inheritance relationship seems to express the fact that the subclass has "additional constraints" relative the superclass. If Polygon were a class in itself rather than an interface:

  • Polygon (anything goes, as long as it is convex)
  • Quadrilateral (additional constraint of having only 4 edges)
  • Rectangle (additional constraint of having Pi/2 angles)
  • Square (additional constraint of having identical edge lengths)

Basically, I would:

Either just program out the Quadrilateral, remove the setters and make all instance variables final. This gets rid of phenomena whereby you get uncontrolled changes in a Square if someone changes the sides individually. (Generally finals are a Good Idea). Subclasses then reduce to just having special constructors.

Or program out the Quadrilateral only, do not subclass, give it boolean checks: isRectangle(), isSquare(), and maybe leave the setters. But that sounds less elegant.

Also recommending Bertrand Meyer's "The many faces of inheritance: a taxonomy of taxonomy" (1996) if you can find it, it's paywalled but there is probably someone with IEEE Xplore access nearby.

Does Square correctly inherits from Rectangle?

Does Square correctly inherits from Rectangle?

No, but it's close. Here are the changes for Square:

function Square(size) {
Rectangle.call(this, size, size);
}

Square.prototype = Object.create(Rectangle.prototype);
Square.prototype.constructor = Square;

Calling Rectangle to create Square's prototype is a sadly-common anti-pattern; what if it actually does something with the arguments we're not giving it when doing that?

Instead, you create an object that uses Rectangle.prototype as its prototype, using Object.create. Then you update the constructor property on that prototype so it's pointing at the right function. Then when you actually have an object to initialize (e.g., in the Square function), you call Rectangle from Square to initialize its bit, then do any further init Square needs afterward.

Since it's a bit verbose, before I switched to ES2015 class syntax I used a helper script for this called Lineage. That bit creating the prototype can be isolated into a function, which also gives us an opportunity to support IE8 by working around the fact it's missing Object.create:

function derivePrototype(parent, child) {
var proto, ctor;
if (Object.create) {
proto = Object.create(parent.prototype);
} else {
ctor = function ctor() { }; // * See note
ctor.prototype = parent.prototype;
proto = new ctor;
ctor = null;
}
proto.constructor = child;
return proto;
}

Then

Square.prototype = derivePrototype(Rectangle, Square);

* ctor = function ctor() { }; creates two functions on IE8 (details), but it's harmless as neither is retained, and the function does get a name.


Or of course, you can use ES2015+:

class Rectangle {
constructor(length, width) {
this.length = length;
this.width = width;
}

getArea() {
return this.length * this.width;
}
}

class Square extends Rectangle {
constructor(size) {
super(size, size);
}
}

All major modern browsers support this native now (in 2019), but of course, older browsers like Internet Explorer don't (not even IE11), so if you need to target older browsers, you'd need to transpile (for instance, with something like Babel).

Should class Square publicly inherit from class Rectangle?

This is one of the key principles in OO design that I find gets handled incorrectly. Mr Meyer does an excellent job of of discussing it the book you are referring to.

The trick is to remember that the principles must be applied to concrete use cases. When using inheritence, remember that the key is that the "is a" relationship applies to an object when you want to use that object as a ... So whether a square is a rectangle or not depends on what you are going to be doing with rectangles in the future.

If you will be setting width and height of a rectangle independently, then no, a square is not a rectangle (in the context of your software) although it is mathematically. Thus you have to consider what you will be doing with your base objects.

In the concrete example you mention, there is a canonical answer. If you make makeBigger a virtual member function of rectangle, then each one can be scaled in a way that is appropriate to a class. But this is only good OO design if all the (public) methods which apply to a rectangle will apply to a square.

So let's see how this applies to your efforts so far:

  1. I see this kind of thing in production code pretty often. It's excusable as a kludge to fix a gap in an otherwise good design, but it is not desirable. But it's a problem because it leads to code which is syntactically correct, but semantically incorrect. It will compile, and do something, but the meaning is incorrect. Lets say you are iterating over a vector of rectangles, and you scale the width by 2, and the height by 3. This is semantically meaningless for a square. Thus it violates the precept "prefer compile time errors to runtime errors".

  2. Here you are thinking of using inheritance in order to re-use code. There's a saying "use inheritance to be re-used, not to re-use". What this means is, you want to use inheritance to make sure the oo code can be re-used elsewhere, as its base object, without any manual rtti. Remember that there other mechanisms for code re-use: in C++ these include functional programming and composition.

    If square's and rectangles have shared code (e.g. computing the area based on the fact that they have right angles), you can do this by composition (each contains a common class). In this trivial example you are probably better off with a function though, for example:
    compute_area_for_rectangle(Shape* s){return s.GetHeight() * s.GetWidth());}
    provided at a namespace level.

    So if both Square and Rectangle inherit from a base class Shape, Shape having the following public methods: draw(), scale(), getArea() ..., all of these would be semantically meaningful for whatever shape, and common formulas could be shared via namespace level functions.

  3. I think if you meditate on this point a little, you'll find a number of flaws with your third suggestion.

    Regarding the oo design perspective: as icbytes mentioned, if you're going to have a third class, it makes more sense that this class be a common base that meaningfully expresses the common uses. Shape is ok. If the main purpose is to draw the objects than Drawable might be another good idea.

    There are a couple other flaws in the way you expressed the idea, which may indicate a misunderstanding on your part of virtual destructors, and what it means to be abstract. Whenever you make a method of a class virtual so that another class may override it, you should declare the destructor virtual as well (S.M. does discuss this in Effective C++, so I guess you would find this out on your own). This does not make it abstract. It becomes abstract when you declare at least one of the methods purely virtual -- i.e. having no implementation

    virtual void foo() = 0; // for example
    This means that the class in question cannot be instantiated. Obviously since it has at least one virtual method, it should also have the destructor declared virtual.

I hope that helps. Keep in mind that inheritence is only one method by which code can be re-used. Good design comes out of the optimal combination of all methods.

For further reading I highly recommend Sutter and Alexandrescu's "C++ Coding Standards", especially the section on Class Design and Inheritence. Items 34 "Prefer composition to inheritence" and 37 "Public inheritence is substitutability. Inherit, not to reuse, but to be reused.

Correct way to implement inheritance in C#

I don't agree that Square shouldn't provide Width and Height properties since squares absolutely do have a width and height just like any rectangle. The only difference is that they are necessarily the same.

The way I'd fix it, is to either make a Rectangle immutable, or allow Square to override the properties:

public class Rectangle
{
public virtual int Width { get; set; }
public virtual int Height { get; set; }

public Rectangle(int width, int height)
{
Width = width;
Height = height;
}
}

public class Square : Rectangle
{
public override int Width
{
get { return Side; }
set { Side = value; }
}

public override int Height
{
get { return Side; }
set { Side = value; }
}

public int Side { get; set; }

public Square(int side)
: base(side, side)
{
Side = side;
}
}

What case of inheritance will be correct, rectangle from Square or conversely?

mathematically, a square is a rectangle.

but oo-wise, a square is not a rectangle because it will break the liskov substitution principle.

see also the circle ellipse problem.

How to design a Square class that inherits from Rectangle class

Design-wise I feel the constructor Rectance( double width ) is un-natural for a rectangle and would remove it.
The constructor for the Square should look like this:

 public Square(double side) {
super(side,side); // width == height
this.shapeName = "Square";
}

To clarify inheritance even more, you could also replace the line this.shapeName= "Rectangle"; with this.shapeName= getClass().getSimpleName(); and remove this.shapeName = "Square"; from Square's constructor.

Type error when dealing with collections of inherited objects

This is called covariance/invariance/contravariance.

A list of rectangles is not a list of squares or vice versa: Lists are invariant.

Plain jane data types.. yes, there all squares are also rectangles: These are covariant.

That's because.. they fundamentally are. Here, let's take a trip to imaginary land and, nestled between unicorns and rainbows, we act as if lists of Xs and lists of Ys are covariant on the data type, e.g. that a list of squares is also a list of rectangles:

List<Square> squares = new ArrayList<Square>();
squares.add(new Square(20));
List<Rectangle> rectangles = squares;
rectangles.add(new Rectangle(10, 30));

Square s = squares.get(1); // UHOH!!!!!

And as we retrieve our square from our list of squares and notice the object we so obtain does not, in fact, have equal sides, our imaginary world crashes and disappears in a poof of smoke and leprecaun tears.

Awwwwww!


So, what's going wrong here?

The add (and add-likes such as addAll) op is in fact contravariant. The relationship is reversed! If I add a square to a list, that's okay.. if it is a list of square or any supertype. You can add squares to a list-o-squares, or a list-o-rectangles, or a list-o-objects. This is in reverse from the get(int idx) operation, which is covariant: I can assign the result of listOfSquares.get(0) to Square, or Rectangle, or Object.

In java, you pick your own variance. The above code snippet from unicorn land does not compile here in the real world. Specifically, the line List<Rectangle> rectangles = squares; won't compile, because by default generics acts invariant, thus making a list of squares not compatible with a list of rectangles.

But you control it, you can change it. Here, let's introduce covariance:

List<Square> squares = new ArrayList<Square>();
squares.add(new Square(20));
List<? extends Rectangle> rectangles = squares;
rectangles.add(new Rectangle(10, 30));

Square s = squares.get(1); // UHOH!!!!!

We have now introduced covariance: The definition of the rectangles object reads: "Covariant on rectangle". This is fine and now this line compiles and runs just fine.

But the error moves: Java knows that given that you demanded covariance, that all add methods need to disappear, because those aren't compatible with the notion of covariance. And so it is: The line rectangles.add(new Rectangle(10, 30)) will not compile. In fact, for any List<? extends Whatever>, the add method is not invokable (unless you pass null, but that's a fluke that's mostly irrelevant to variance).


You can even opt into contravariance:

List<Square> squares = new ArrayList<Square>();
squares.add(new Square(20));
List<? super Rectangle> rectangles = squares;
rectangles.add(new Rectangle(10, 30));

Square s = squares.get(1); // UHOH!!!!!

And now we have a list of contravariant rectangles. As expected, the world is now flipped upside down: Add is fine in this world. But get() is an issue.

First of all, a list of squares is not assignable to a list of rectangles if we're in contravariant world, so that line won't compile. But a list of objects would be, so, this will compile:

List<Object> o = new ArrayList<Object>();
List<? super Rectangle> r = o; // this is fine!
r.add(new Rectangle(10, 20)); // so is this
Rectangle z = r.get(0); // but not this

now add works fine, but get doesn't. Which shows up not as 'you cant call get at all', but as: the type of the 'get' invocation is just java.lang.Object.

This makes sense: You can assign a list of rectangles or a list of objects to a List<? super Rectangle> variable, because adding a rectangle to either of these is fine. When getting values out, best I can do for you is to guarantee you that no matter what, it's an object, so that's all you get.

So, how do I fix this?

Weirdly, you probably can't. It sounds like updateRectangles needs to both 'read' items from the list as well as 'write' items to it.

If all you were going to do is read from the list and you want the guarantee that whatever falls out, it's at least a rectangle, that's covariance, so you tell the compiler you opt into that: void readRectangles(Collection<? extends Rectangle> collection) {} and you can pass a list-o-squares to this method just fine (and if you try to add to the collection, javac won't let you).

If all you were going to do is write, then.. opt into contravariance: void addSquares(Collection<? super Square> collection) {} and you can call .add(new Square()) on the collection just fine. You can pass your list-o-rectangles or your list-o-squares.

But you want both, so all you get is invariance. What you want isn't really possible, not without resorting to unsafe casts or other hackery.

If you describe exactly what updateRectangles actually does, we can help. If you never call .add(), go with Collection<? extends Rectangle> and you're good to go.



Related Topics



Leave a reply



Submit