Timothy Budd, in his book “Understanding Object-Oriented Programming with Java” (Addison-Wesley, 2000), makes the following distinction:
The term subtype is used to describe the relationship between types that explicitly recognizes the principle of substitution. That is, a type B is considered to be a subtype of A if two conditions hold. The first is that an instance of B can legally be assigned to a variable declared as type A. And the second is that this value can then be used by the variable with no observable change of behaviour.
The term subclass refers merely to the mechanics of constructing a new class using inheritance, and is easy to recognize from the source description of a program by the presence of the keyword extends
. The subtype relationship is more abstract, and is only loosely documented directly by the program source. In the majority of situations a subclass is also a subtype. However … we will discover ways in which subclasses can be formed that are not subtypes. In addition, subtypes can be formed using interfaces, linking types that have no inheritance relationship whatsoever. So it is important to understand both the similarities and differences between these two concepts
Understanding Object-Oriented Programming with Java
— Timothy Budd
He then identifies the following forms of inheritance, noting that this list is not exhaustive, and that in some situations more than one category might apply:
Contents
Inheritance for specialisation
- The child class is a special case of the parent class
- The child class satisfies the parent specification in all relevant respects (so no extra methods, but may override existing ones)
- Parent methods when invoked do not realize they are manipulating an instance of the child.
Examples
- a class
Quadrilateral
has an areamethod
which in classRectangle
and classSquare
can be overridden by a more efficient implementation - a class
List
has afindSmallest
method which in the classOrderedList
is overridden by a more efficient implementation
In this form, the subclass is a subtype, and the principle of substitution holds.
Inheritance for extension
- The child class adds extra behavior to that of parent.
- The child does not change any inherited parent behaviour (i.e. no overriding)
Example
- class
Student
extends Person with additional student-specific methods
Again, the subclass is a subtype, and the principle of substitution holds.
Most concrete code (implementation) inheritance is a combination of the above two forms.
Inheritance for specification
- The parent defines behaviour that is implemented in the child class but not in the parent (although the parent may have some implemented methods)
- All child classes have a common specification or interface
- The child class is a realization of the parent
- The child class provides application specific behavior
Java provides two mechanisms for this:
- implements an interface
- extends an abstract class
Again, the subclass is a subtype, and the principle of substitution holds.
Inheritance for construction
- The child class inherits most of its functionality from its parent
- Opportunistic use of parent code because it is roughly what the child needs, but no real is-a relationship
Example :
- Class
Circle
inherits from classPoint
(adding diameter, area, etc). Stack
implemented by inheriting fromArrayList
(see next session)
Here, the subclass is not a subtype, and the principle of substitution does not hold, so this is widely regarded as a dangerous practice. It would not be acceptable to replace a Point
by a Circle
in most circumstances!
Inheritance for limitation
- Occurs when the behaviour of a subclass is more limited than that of parent
We sometimes get special cases in which the specialisation does not provide all of the behaviour of the more general class: for example, ostriches are birds that have lost the ability to fly.
In some languages, a subclass can “turn off” an inherited method so that it is not available to clients, for example by overriding a public method to make it private. This is illegal in Java, because it breaks the substitutability principle. For example, given:
public class Bird { public void fly() { // . . . } } public class Ostrich extends Bird { private void fly() // NOT legal Java! }
what is supposed to happen if a client writes:
Bird b = new Ostrich(); b.fly();
The static type of b
is Bird
, so the compiler allows the call to fly, but the dynamic type does not support this method. There are other ways of achieving this sort of effect that are also banned by Java language rules.
Here, the subclass is clearly not a subtype, and the principle of substitution does not hold, so this is widely regarded as a dangerous practice, even if the programming language allows it.
A better solution is to re-organise your class hierarchy, so that Bird
has subclasses FlyingBird
(where the fly method is introduced) and NonFlyingBird
, making Ostrich
a subclass of the latter.
If you want to re-use some of the code of an existing class in the definition of a new one, and you cannot modify the organisation of the existing classes (e.g. they are part of a library that you cannot modify) a much better solution is to use composition, as discussed in a later session.
Inheritance for combination
- The child class inherits features from more than one parent class
- A new abstraction is formed by combining features of two or more abstractions
This is allowed in Java by:
- a class extending a parent class and implementing an interface (or interfaces)
- a class implementing two or more interfaces
Conformance to an interface automatically creates a subtype, so the substitution principle will always hold in the second case. In the first case, whether a subtype relation holds between the parent and child classes depends on which other form (or forms) of inheritance applies between these two classes.
Inheritance in Java
Technically, in Java, we have two specific ways of managing inheritance:
- Interface inheritance (using the
implements
keyword)- The interface defines the behavior expected of a class (just the method headers or signatures)
- A class implements this interface, providing code for each of these methods…
- This supports abstraction, low coupling, “pluggability”, and flexible designs
- Implementation inheritance (using the
extends
keyword)- A base class (superclass) provides some code
- A subclass extends this, adding extra code, or modifying existing code
- This provides for code re-use, which is important in practice. But in some ways this is problematic from a design viewpoint (it can produce high coupling – which we discuss in the next unit – between the base class and its subclasses)
Notice that what we call “interface inheritance” uses implements
, and “implementation inheritance” uses extends
! Be careful not to get confused over this!