The Legacy of Inheritance
Consider the following code (public fields for brevity):
public class Square {
public int base;
public int getArea() {
return base * base;
}
}
public class Rectangle extends Square {
public int height;
@Override public int getArea() {
return base * height;
}
}
public class Parallelogram extends Rectangle {
public double angle;
}
Now how do we implement a Rhombus? Is it a Square extended with an angle (and overridden area calculation) or a Parallelogram with conditions on setting the properties so that the invariant is preserved (which is why we should have accessors, by the way)?
Well, the correct answer is neither, even though we have a nice sequence of extensions. The problem is that we have been led astray by the extension mechanism and violated the "is a" rule for subclassing and ended up with a corrupt type system. Clearly a Parallelogram is not a Rectangle which equally clearly is not a Square so a subclass instance may not safely be used in place of a superclass instance. Reversing the class hierarchy solves the issue, however it creates subclasses that are restrictions of the superclass rather than extensions.
An acquaintance of mine who is highly experienced in creating standardized components for information interchange once claimed that it only works to inherit properties by restriction. This is well worth considering, although I'm not entirely convinced because it causes a different set of problems when restriction means "that particular property is not used", as it so often does in information interchange scenarios. In that case it is usually not interesting nor useful to know what the superclass is. In our case at hand it will work because the subclasses actually have a meaningful and useful value of the property but it is restricted by an invariant, so it is also useful to be able to view them as instances of the superclass.
Let's try coding again:
public class Parallelogram {
protected int base;
protected int height;
protected double angle;
public void setAngle(double angle) {
this.angle = angle;
}
}
public class Rectangle extends Parallelogram {
@Override public void setAngle(double angle) {
// What should we do here?
}
}
...
Oops, this doesn't seem to work well, either. Obviously we can't just inherit the setAngle method or we could end up with a corrupt Rectangle, nor is there any reasonable action we can take. We can get out of our pickle, however, because constructors are not inherited! Simply make our instances immutable:
public class Parallelogram {
public final int base;
public final int height;
public final int angle;
public Parallelogram(int base, int height, double angle) {
this.base = base;
this.height = height;
this.angle = angle;
}
public int getArea() {
return base * height;
}
}
public class Rectangle extends Parallelogram {
public Rectangle(int base, int height) {
super(base, height, Math.PI/2);
}
}
public class Square extends Rectangle {
public Square(int side) {
super(side, side);
}
}That seems to work, and the implementation of getArea() is actually profitably inherited. But we still have a problem in Java with a Rhombus, since a Square is both a Rectangle and a Rhombus but a Rhombus is not a Rectangle. To solve that would require defining the types as interfaces and have separate implementation classes (now I understand why that is often recommended :-) ).
If we had chosen to implement Parallelogram with a field for the length of the side instead of the height, the formula for the area would have been base*Math.sin(angle)*side. Even if this would have worked for our Rectangle, it would have been inefficient (although when the fields are immutable it could probably be optimized by the compiler and/or JVM).
On the whole, I believe it is seldom profitable to inherit the implementation of a method, if you have a good example to the contrary, please share it.
I think that overriding all public methods would be preferable, even if you just decide to call super's version, at least it would show that you gave some thought to whether it was correct or not. Without having done an exhaustive study, I believe this is especially true for interfaces that indicate that a class "is" something rather than that it "is a" something, i.e. those interfaces whose name usually end in "able". Try Cloneable, Externalizable, Runnable and Printable.From http://tobega.blogspot.com/2008/08/legacy-of-inheritance.html
I have been writing code since 1980. In 1999, I learned Java and after discovering IntelliJ the love was complete. XML and XSLT was a revelation in 2000, too bad the standards committees have done so much damage since. Contributed to the Flying Saucer open source XML and CSS rendered and published an open source XSLT generator, weffo. Currently employed by Google. Torbjörn is a DZone MVB and is not an employee of DZone and has posted 8 posts at DZone. You can read more from them at their website.
- Login or register to post comments
- 2521 reads
- Printer-friendly version
(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)










Comments
Brian Sayatovic replied on Wed, 2008/08/20 - 8:21am
Geometrically speaking, there are IS-A relationships, with quadrilateral being the most basic, followed by trapezoid, parallelagram, rhombus/rectangle, and square. But when we speak of these geometrically, we talk only about observing their properties.
Your code above shows mutable classes. We don't often see a square on a piece of paper and then chose to warp it. The gemoetrically observable inheritence of their qualities only holds so long as you don't morph the shapes. If your classes were immutable, wouldn't that solve much of your problem?
public class Quadrilateral { // ... } public class Trapezoid extends Quadrilateral { // ... } public class Parallelogram extends Trapezoid { private int a; private int b; private float angle; public Parallelogram(int pA, int pB, float pAngle) { ... } } public class Rhombus extends Parallelogram { public Rhobus(int pSide, float pAngle) { super(pSide, pSide, pAngle); } } public class Square extends Rhombus { public Square(int pSide) { super(pSide, 90); } }Thomas replied on Wed, 2008/08/20 - 9:23am
Ever heard of java.lang.Object, java.io.InputStream? :-) Just search for "extends" in any good software.
DougM replied on Wed, 2008/08/20 - 11:42am
mark taylor replied on Wed, 2008/08/20 - 2:38pm
It's interesting how people rediscover this every so often. The issue is: inheritance is not subtyping. It's a subtle, but important distinction. It's been studied extensively by programming language researchers.
http://www.amazon.com/Foundations-Object-Oriented-Languages-Types-Semantics/dp/026202523X/
Steven replied on Wed, 2008/08/20 - 8:09pm
your just using it wrong
either learn proper OO or go back to your C
Artur Biesiadowski replied on Thu, 2008/08/21 - 6:22am
On my university, there was a teacher who got it even more wrong. In one of the exercises class Point was given (with x,y inside) and then Line extended it (adding x1,y1) and then Triangle was inherting from Line (adding x2,y2)... Circle was also extending Point (with radius added). Then, shape of circle + triangle together was created by multiple inheritance (it was c++ code).
He was not teaching me, but my younger friend - I had to explain afterward to that friend that inheritance is not THAT stupid and there are ways to use it properly...
Jason Marshall replied on Thu, 2008/08/21 - 1:08pm
I beg pardon, but if you study type theory a bit more, I think you'll find that all subtypes are in fact restrictions on their supertypes. A supertype is an abstract concept, like a 'mammal'. Some mammals can fly. Some can hold their breath for hours at a time. But there is no mammal that can do both.
Most people I run into who have serious, deep problems comprehending generics have this same failure of reasoning. They're used to subclassing to 'add things' to a type, which is exactly how most of us code. When you add a function to one subclass, you're in a way saying none of its siblings have/need this functionality (otherwise, you would have put it in the base class, right?). In essence, you're carving out territory for this class at the expense of its peers.
Torbjörn Gannholm replied on Sat, 2008/08/23 - 1:56pm
in response to: thomasmueller
Ever heard of java.lang.Object, java.io.InputStream? :-) Just search for "extends" in any good software.
[/quote]
How do you logically deduce that the presence of the extends word signifies successful inheritance of implementation? Without overriding the methods, the subclass would be useless, n'est-ce pas?
Steven replied on Sat, 2008/08/23 - 8:44pm
the subclass isnt useless if u dont override methods.
your extending the class to provide more or modified functionality