I'm writing documentation for an object-oriented language, and I wonder what kind of classes would be a good example for inheritance.
Some common examples:
class Person {
}
class Employee extends Person {
}
Currently my favorite, but I don't like Person->Employee because 'Employee' does not exactly look like fun.
class Bicycle {
}
class MountainBike extends Bicycle {
}
I found this in some Java tutorial, but it's not really obvious what attributes a bike should have.
class Animal {
}
class Bird extends Animal {
}
Same as the bicycle.
class A {
}
class B extends A {
}
Too abstract. The main problem is that such a class will need even more abstract attributes and methods.
Does anyone have a better example for a simple class hierarchy?
I use to show Chess pieces. The base class is the general ChessPiece, King, Rook, Bishop, etc inherits.
What I like about this example:
Hierarchy of Shapes
I completely understand the concerns regarding the relationship between Rectangles and Squares regarding Liskov's substitution principle. However, there are ways to preserve this principle and still have Square as subclass of Rectangle. For example (Python), assume the following:
We can define Square the following way:
This way we can always use an Square instance whenever a Rectangle instance is needed, thus preserving Liskov's.
Now, from my experience, we can also use Inheritance to enforce a policy (usually restrictive). For example, assume we have a Shape class with a color property and corresponding setter (setColor).
Assume we want to create a "RedShape" class. How can we prevent client code from changing the color? Answer: de-active the setColor method by overriding it.
We redefined setColor in RedShape to do nothing.
The setColor in RedShape overrides the one in Shape so client code cannot change the color of RedShape instances. In a way, we created a more restrictive subclass because we do not have the ability of changing its color, but it does not violate the substitution principle.
This model is useful when we have base classes that are generic (not abstract) and we create specialized classes to control or limit the features available in the base class. You can create a chain of subclasses increasingly enabling features available in the base class, or you can create classes that offer different combinations of features available in the base class.
Before I get flamed, let me reiterate that this usage is a pragmatic model that takes advantage of the mechanism of inheritance and polymorphism for control. It is far removed from the romantic notion of inheritance of creating new classes by enhancing or refining existing ones.
Many people use the Shapes example, but that is in fact a dangerous one. The problem arises when you intuitively decide that a square is a subclass of rectangle.
When it comes to behavior, a square is more limited than a rectangle, breaking substitutability. For example, we could ask a rectangle object to change its height. If a square is a subclass of rectangle, that means we should be able to ask the same of a square. However, changing the height of a square would mean its not a square anymore! Of course, we could increase the width accordingly, but that's not what we would expect when we were to ask an object of declared type rectangle, which is actually a square underneath, to change its height.
It's called the Liskov substitution principle, and you should be aware of it when doing any serious OO development.
Squares are, of course a subset of rectangles, instead of a subclass. This is the difference between data-oriented and behaviour-oriented approaches.
Like Jon, I prefer Streams as an example. It's not difficult to explain, even to non-programmers, and its cleary behaviour-oriented, avoiding the counter-intuitivity of the shapes-example.
I agree with Jon Skeet on his streams example. Perhaps it's not perfect, but it has one advantage over most of of the examples here:
It is realistic
Bicycles, persons or animals, shapes or weapons just wouldn't be modelled by inheritance in real projects. (shapes in particular, are downright dangerous, because it doesn't work.)
That's my pet peeve with inheritance. It is too often taught as something that must be used to express every hierarchy you can find. An employee is a person, right? So the Employee class must inherit from a Person class. But a person is also a LivingCreature, so we'd better have one of those classes too. And a LivingCreature is also an Organism, so there we have another class. And an Organism is.... feel free to continue.
I think it'd be nice if someone, somewhere, actually taught inheritance by explaining when it should be used, and not just how you can force it down over any hierarchy, whether it's beneficial or not.
Streams (or devices as in ChrisW's example) have the advantage that they make sense. You want to be able to treat all streams the same, whether they're connected to a memory buffer, a file or a network socket. And all hardware devices do have a lot of behavior in common that could plausibly be factored out into a Device base class.
The best one I have always used has been Shapes.
You can then have
etc.
Methods are then also easy to guess at.
The Animal class is the classic example of class inheritance for a number of reasons.
First, there are obvious ways to extend the underlying animal class. You'll likely start with sub-classes such as Mammal, Bird, Crustacean, etc.
Some classes, such as Mammal, will extend Animal by adding attributes that are fairly obvious ("Warm-Blooded", etc.).
Other, more problematic, issues that are quite common in developing a class hierarchy are quite obvious if you illustrate with animal inheritance - and this is a good thing for purposes of explanation. Birds fly, right? Well, not all birds... So, how do you represent flying? There are, of course, classic solutions and a wealth of discussion information online about how to solve the problems and tradeoffs that each solution introduces.
Thus, I would highly recommend using "Animal" as your example because of the richness of available information and examples.