6/15/2007

The Pitfalls of Inheritance

In Object Oriented Programming, inheritance is a commonly used mechanism to model the relationship between two types. However, modelling such relationship without realizing the impact on the overall application may result in unexpected problems. In this article, I will try to present some internal details of inheritance mechanism based on the Java language and highlight some of the problems that a developers should be aware of when using inheritance of types.

Inheritance Defined…
One of the first questions that we should answer is what do we mean by inheritance. The inheritance is a modelling technique of expressing the relationship between a generalized type and a specialized type. This in essence is a way of expressing IS A relationship. For example, we can say Car IS A Vehicle. In such situations, we will say Car inherits from Vehicle. In the world of Java language, this inheritance relationship is expressed with the following semantics.



class Car extends Vehicle {


In this model, Car is a subclass of the Vehicle super class. Now the next question is what we inherit. By default, the subclass inherits all the interfaces and the implementations from the super class. Note that, the use of the word "interface" in this context does not refer to the interface type specific to the Java language. In OO, the "interface" is a function/method that a particular class exposes to the external world. Coming specific to Java, the inheritance relationship guarantees the following:


*The sub class inherits all the public methods and the implementation in the super class.
*The sub class inherits all the protected methods and their implementation in the super class.
*The sub class also inherits all the public and protected member variables from the super class.
*However, constructors are not part of this inheritance model. We shall also see later the invocation relationship of constructors that exists between a sub class and its immediate super
class.

These consequences of inheritance also imply the following things that are vital to how we can use a super class and any sub class of it. In general, when you model and implement an inheritance relationship, you guarantee the following:


*The sub class is capable of accepting all the messages that the super class accepts.
*The sub class can replace the super class anywhere the super class is called for without
affecting the final outcome.



Modifying the Super Class Behavior…
With inheritance mechanism, although the sub class by default inherits all the interfaces and implementations of the super class, it is possible to override any of the implementations in the sub class. It is also perfectly possible to extend the interfaces by adding new methods to the sub class. Both of these are mechanisms to modify or extend the functionality of the super class. For example, consider the following inheritance scenario in Listing 1. public class Vehicle
{
public void stop()
{
//stop the engine

}

public void accelarate()
{

}
}


public class Car extends Vehicle
{

public Car()
{

}

public void stop()
{
//stop the engine
//lock the steering wheel
}

public void switchOnWiper()
{

}

}
Listing 1: The Vehicle-Car interface inheritance


In this example, we have modelled Car IS A Vehicle. The Car class overrides the stop() method implementation of its super class Vehicle. However, it decides to reuse the accelarate() implementation from its super class.
As a result of this inheritance relationship, we will be able to write a VehicleTester class in the following form.
public class VehicleTester
{

public void testVehicle(Vehicle vechicle)
{
//test the accelarate behaviour
vechicle.accelarate();
//test the stop behaviour
vechicle.stop();
}

}
Listing 2. The example benefits of using inheritance relationship


In this example, you can immediately see the benefits of inheritance relationship. We could write a single method accepting a super class type and we are able to pass any sub class to the same method. This greatly reduces the strongly typed nature of code and increases the flexibility of the application module.

Restricting inheritance…
Sometimes in your design you would like to stop some classes and methods being inherited. The final keyword in Java achieves this functionality. When you declare a class to be final, no other sub class of it can be created. Similarly, when you declare a method to be final, no sub class can override that method.


Overriding member variables…
So far we have talked about how can you override the methods declared in the super class. It is also possible to hide the member variables declared in the super class, by declaring a member variable in the sub class with the same name as in super class. The type of the field does not matter, only the name needs to match. Notice that when you hide a member variable in the sub class, you can still access the super class variable by using the super keyword. This means that the member variables cannot be overridden but can only be hidden. It is important to notice this subtle distinction between overriding and hiding.
The logic behind that Java allows the hiding of member variables, is that you can then create a sub class of any super class without detailed knowledge of its internal implementation. This is more in the line of encapsulation.


How it works…


We have just seen how powerful inheritance mechanism is and how relatively it is to implement in real world. We will later in this article examine some of the pitfalls with this inheritance mechanism but for the time being, let us see how the inheritance works in Java.


Object Initialization and Inheritance


When an object is initialized, all the instance variables defined within the class of that object and also all the instance variables defined in its super class get initialized. Consider the following example in Listing 3.


public class Vehicle
{
private String registration;

public void accelarate()
{

}
public void stop()
{
//stop the engine

}
}


public class Car extends Vehicle
{
private String model;

public Car()
{

}

public void stop()
{
//stop the engine
//lock the steering wheel
}

public void switchOnWiper()
{

}


}
Listing 3 A Simple inheritance model


Following this example, we would do the following to create an instance of the Car object: //create a new instance of the Car object
Car car = new Car();
In this, the instance variable of the Car class named model and also the instance variable registration of the super class Vehicle both will get initialized to their default values. By default, the Java Virtual Machine will allocate enough space for all the instance variables of the Object’s own class and all the instance variable in all its super classes. Do not forget that in Java you can have multi-level inheritance, that is a Car IS A Automobile IS A Vehicle. In such cases, the same initialization mechanism will be followed. The initialization chain finally leads to the java.lang.Object class as all the classes in Java implicitly inherits from the Object class.
The important thing is however the order of initialization. According to Java Language Specification, the initialization starts with super class fields and end with the fields declared in the Object’s own class. Thus, in our previous example, the order of initialization will be:
1) All variables in the Object class. (No fields declared)
2) All variables in the Vehicle class. (field registration).
3) All variables in the Car class (field model).
The logic behind this order of initialization is to ensure that you can use a super class variable within a sub class initialization properly.

The Initialization Mechanism…
In Java, the initialization of objects can be done in two ways:
* The static initialization blocks
* The constructor
The static initialization block is invoked only when the Class Loader in Java loads the class. The constructor is not invoked unless you explicitly invoke them in order to initialize an instance of an Object.
In Java the first thing that a constructor does is to invoke another constructor in the super class. In order to fully understand this, I would try to iterate the mechanism in the following points to note.
* In Java if no constructor is explicitly defined for a class, the compiler provides a default no-argument constructor. However, if you explicitly specify a public constructor, compiler will not place any default constructor.
* When the constructor of the sub class is invoked, at first the compiler will invoke the default constructor in the super class recursively up in the tree.
* The super class’s constructor can also be invoked explicitly by using the super() key word.
From within a constructor, you are free to call a super class constructor with any number of arguments. It does not have to be the default constructor only.
* It is important however to note that the order of constructor invocation starts from the Object’s class and proceeds recursively up in the tree. However, if you remember that the order of instance variable initialization proceeds from the base class down to the Object’s class. (Figure 1).
* The previous point coincides with the fact that eventually before a constructor of the Object’s class has finished its job, a constructor of the super class is always called, which will initialize the super class fields.






Figure 1: The Constructor invocation and Object initialisation order
Now that we understand how the inheritance in Java really happens behind the scene, it is time to think more about the implications. At first sight, inheritance is an attractive mechanism. But on second thoughts, we will reveal some of the problems that might arise with the user of inheritance.

The Problem Begins…
The above examples were easy to understand in terms of modelling and coding. What is difficult is how all this magic happens behind the scenes. In fact, we have seen a lot more of what goes on behind the scene than we would think normal. Now, we will probe into the depth of the black magic and who knows we might get scared and leave the stage of inheritance magic all together.

Problem 1
Let us return to our example, and now we come up with something like the following in Listing 4.
public class Vehicle
{
private String registration;

private Vechile(){ }

public void accelarate()
{

}
public void stop()
{
//stop the engine

}
}


public class Car extends Vehicle
{
private String model;

public Car()
{

}

public void stop()
{
//stop the engine
//lock the steering wheel
}

public void switchOnWiper()
{

}


}
Listing 4. New version of the Vehicle-Car relationship implementation


Nothing has changed except we have added two member variables to both the classes and added constructors for the classes. However, the problem begins now that the code does not compile any more!


Lesson 1
The super class in an inheritance relationship cannot have a private constructor. The argument here is that the private constructor stops us from directly instantiating any object of that class. If we really don’t want anyone to create a new object of any class, it cannot possibly be used to represent any Object in an IS-A relationship.
The exceptions to this rule are however the abstract classes and classes that prefer to provide a factory method to obtain instances of that class. The abstract classes are never intended to be initialized as they are abstracts and the compiler will never allow you to do so.
On the other hand, some developers are fanatic about providing factory method as opposed to constructors as means to instantiate objects. It is like providing a static method such as getInstance() as we are used to in implementing "Singleton pattern". The argument here is that you can always change the mechanism of creating instances of that class without affecting the clients of that class.

Problem 2
Now we correct the previous problem and just remove the private constructor; after all it was just a do nothing constructor. But we decide to add another method stop() in the Car class, which returns a boolean if the Car has stopped. Here is code in Listing 5. public class Vehicle
{
private String registration;

public void accelarate()
{

}
public void stop()
{
//stop the engine

}
}


public class Car extends Vehicle
{
private String model;

public Car()
{

}

//The new method//
public boolean stop()
{
//stop the engine
//lock the steering wheel
}

public void switchOnWiper()
{

}


}
Listing 5: Another version of the Vehicle-Car relationship


This class will also not compile. The problem is that the super class already has a method with the same name(stop) but different return type (void).


Lesson 2
The sub class cannot declare a method with the same name of an already existing method in the super class with a different return type. The sub class can however declare a method with the same signature as in super class. We call this "Overriding".
This is called a "fragile base class" problem. The base class is fragile because before you can extend it, you may require the full knowledge of all its sub classes. Otherwise, you might end up in a situation where you add a method with the same signature as in one of the base classes and
the application no longer compiles!


The finale…
You might think that the problems so far described are not really problems as being a Java developer; you would probably know all of it, but it is important to notice that all these, point to a bigger picture and a deeper problem. The problem is that you cannot always trust the inheritance relationship.

* It is too restrictive in the way you have to make the super class available to the sub classes. For example, the presence of a public constructor is a must.
* Both the super class and sub classes need the knowledge of each other before adding any other methods to them. This is fragile in terms of flexibility of design.
* The inheritance in one way violates the principle of encapsulation as sub classes can potentially have access to all data and methods of the super class. However, you can restrict visibility by properly using the access modifiers available in Java.
* The fact that a sub class by default inherits all the implementation and interfaces of the super class makes the constituting API more prone to misuse.

Conclusion
In this article, we have seen the inside of inheritance mechanism in Java and how it works. We have understood some of the problems related to inheritance. A section of the problem arises from the misuse of the inheritance relationship and some are inherent in the inheritance concept itself.
However, as designers and developers we would like to find an alternative. But is there one? There can be arguments both ways. However, to keep the scope of this article simpler, I will just only point to another technique called Composition where one object contains reference to another object. I will present a detailed analysis of Composition and Inheritance in the next article. Until then make sure you understand all that we talked about Inheritance.

1 comment:

Snehasish Roy said...

During method overriding, return type can be covariant.

dzone.com