CSC/ECE 517 Fall 2010/ch1 25 ag
What is Inheritance?
Inheritance, along with polymorphism and encapsulation, is one of the pillar features of Object Oriented Programming (OOP). Inheritance allows a class to reuse, extend or modify the behavior of another class. The term inheritance generally refers to single inheritance, where a sub-class or a derived class inherits methods and attributes from only one parent class.
For example, consider a general class of electrical appliances. Switching an appliance on and off can be considered a standard feature of every appliance. Now, appliances that control the temperature can be construed as electric appliances with some added functionality (increase/decrease temperature). Here is how they are implemented.
class ElectricAppliance { public void on() { ... } public void off() { ... } }
class TempControlAppliance : public ElectricAppliance { public void increaseTemp() { .. } public void decreaseTemp() { .. } }
Consider a task where two specific temperature control devices has to be modeled. Apart from being temperature control devices, they also implement specific tasks related to their domains, eg heating, cooling.
class Heater : public TempControlAppliance { public void heat() { .. } }
class Cooler : public TempControlAppliance { public void cool() { .. } }
If we don't have inheritance, we might have to duplicate all the functions in both Heater and Cooler class. Inheritance is very important to achieve DRY (Don't Repeat Yourself). In the above class hierarchy, TempControlAppliance is super (parent) class for both Cooler and Heater subclass (child).
What is Multiple Inheritance
When a class inherits from more than one class i.e., a child class having more than one parent class, is called multiple inheritance. Though this feature offers more flexibility, it seem to overwhelm most of the developer, so some of the object oriented language designer decided not to support Multiple Inheritance. Java and C# are some of the most famous language which don't support multiple inheritance. We need careful design to take proper advantage of Multiple inheritance, otherwise you might end up in spaghetti code, with classes inheritancing from multiple classes.
Example: Building upon the above example, let us say we have a new device called Air Conditioner which can both heat and cool air. We might have a new class called AirConditioner as follows:
class AirConditioner : public Heater, public Cooler { public void setThresholdTemperature(int temp) { ... } }
Good and bad of Multiple Inheritance
Good:
- Multiple Inheritance strongly supports DRY principle, which leads to better software which is extensible, cohesive, less coupled and modular.
- Multiple Inheritance is very common in real world, in which object takes attributes from different objects.
- Multiple Inheritance is more expressive than Single Inheritane, so it gives flexibility in designing software with complex object relationships.
Bad:
- When a class extends two functions which has same function, it will have two version of the function with same name. There is an ambiguity in resolving which function to call. This is called Diamond Problem(3)
- The class might have more than one path to the top-most parent class because of multiple Inheritance
- Typecasting will become ambiguous in Multiple Inheritance. Refer to Virtual Inheritance for an example of this problem.
- To take complete advantage of Multiple Inheritance needs a thoughtful design. Without a good design, Multiple Inheritance infact might lead to bad code.
- Multiple Inheritance is overwhelming to developers. Requires more experience to properly use Multiple Inheritance.
Implementation issues
One of the most common problem with Multiple Inheritance is called the Diamond Problem(3). This is encountered when a derived class inherits from multiple parent classes, which in turn inherit from one super class. Let us try to understand this problem using an example.
Consider a task of modelling a car. Now a car can broadly be divided into two subclasses, mechanical cars and electric cars.
class Car { public: void start() { cout << "Start with loud noise"; void steer(); }
class MechCar : public Car { }
class ElectricCar : public Car { public: void start() { cout << "start without noise"; }
Now considering modern advancements in vehicle design, a new subclass of hybrid cars have emerged in the market. These cars have traits of both mechanical and electric cars. Here is how this is achieved in C++.
class HybridCar : public MechCar, ElectricCar { // This has two copies of start() }
Here is an illustration of the inheritance hierarchy.
Now if an object of the HybridCar class calls a method of class Car, an ambiguous situation arises. Assuming the method has been overridden by class MechCar and ElectricCar, then it is not clear which version of the method the object is referring to. This is what is known as the Diamond Problem. This problem is addressed in C++ solves this problem through the use of Virtual Inheritance.
HybridCar h; h.ElectricCar::start();
There are also ambiguities during the type casting. For more details about Virtual Inheritance refer here
Multiple Inheritance - Do we really need this?
Many languages which don't support multiple inheritance completely, has some ways of emulating multiple inheritance. We will consider Java as an example which does not support Multiple Inheritance completely and see how we can emulate multiple inheritance in Java. It is not completely true to say that Java does not support multiple inheritance, in fact with interfaces we can have multiple inheritance. Since, Interface just defines the behaviour, it does not have any attributes to describe it, Interfaces are not considered as a Class.
Let us consider the previous example of an AirConditioner:
class AirConditioner extends [______________]
We cannot fit both the classes there since a class in Java can extend from a single class, unlike what we did in C++. But Java classes can implement multiple interfaces.
public interface IHeater { void heat(); }
public interface ICooler { void cool(); } public Heater extends TempControllerAppliance implements IHeater { public void heat() { .. } } public Cooler extends TempControllerAppliance implements ICooler{ public void cool() { .. } } public IAirConditioner { public void setThresholdTemp(int t); }
public class AirConditioner extends TempControllerAppliance implements IHeater, ICooler, IAirConditioner { private ICooler cooler = new Cooler(); private IHeater heater = new Heater(); public void cool() { cooler.cool(); } public void heat() { heater.heat(); } public void setThreshold(int t) { ... } }
To implement multiple inheritance in Java, we need to the following: 1. Make every class implement an Interface 2. Classes extends these interfaces to add logic 3. When a class needs to extend from Multiple interfaces, you can implement those interfaces and delegate the call to the actual implementation. This is achieved through Composition.
We can see that it is possible to emulate multiple inheritance through composition.
Let us take another example and see how we can emulate Multiple inheritance in Java. We are developing a paint application. We have separate tools to draw each primitive shapes.
class Shape { //... } class Square : Shape { // .. } class Circle : Shape { //.. } class Triangle : Shape { //.. } |
class Painter { //.. } class SquarePainter : Painter { void draw(Square s) { } } class CirclePainter : Painter { void draw(Circle c) { } } class TrianglePainter : Painter { void draw(Triangle t) { } } |
Now we need a canvas painter, which should have the capability to draw any of the above shape. In C++, this is pretty straight forward, you define a new class which extends all painters.
class CanvasPainter : SquarePainter, CirclePainter, TrianglePainter { // .. }
How can do the same in Java? Ofcourse, we will use composition, but how will you know which version of draw function to call? May be we can write some thing like this:
if (shape instanceof Triangle) { trianglePainter.draw((Triangle) shape); } else if(shape instanceof Circle) { circlePainter.draw((Circle) shape); }
Unfortunately this is not a scalable solution and for every new tool we add to our paint program, we will have to change this code. This is where design patterns like Chain of Responsibility proves helpful.
Implementing the above code in Java using Chain of Responsibility design pattern.
interface IPainter { boolean canDraw(Shape s); void draw(Shape s); }
All painters will implement the above function:
class TrianglePainter implements IPainter { public boolean canDraw(Shape s) { return s instanceof Triangle; } public void draw(Shape s) { // ... } }
class CanvasPainter implements IPainter { // List<IPainter> = .... // function to add and remove painters public void draw(Shape s) { for(Paint p : painters) { if(p.canDraw(s)) { p.draw(s); return; } } } }
We can see that, chain of responsibility design pattern provides a very neat way to handle multiple inheritance in languages which don't support them completely.
Alternates to Multiple Inheritance
- Composition & Interfaces
Compositions provides a neat way of emulating Multiple Inheritance. With Multiple Inheritance, we bring functionality from different classes to the class which extends them. We can achieve the same using Composition in which one class contains instance of other class, there by importing the capability of all those classes. Since a class is allowed to implement Multiple Interfaces, the class behaves as though it extended from Multiple classes.
- Mixins
Some languages provide mixins as a language feature. Mixins provide another elegant mechanism which mimics Multiple Inheritance. The common problems with Mixin is that it pollutes the class namespace as it imports functions from different modules. This article talks about the limitations of mixins in greater detail.
- Extended Dynamic Proxies
Dynamic Proxy is a technique in which we can dynamically synthesis a new object from existing objects using reflection. With Dynamic Proxies, we can dynamically make a class implement different interfaces. This is achieved through reflection mechanism. This article talks about using Extended Dynamic Proxies to achieve Multiple Inheritance in Java. But this rather a verbose and a complicated solution. This article discusses using Dynamic proxies in greater detail.
Conclusion
We discussed briefly about Inheritance, Multiple Inheritance, its advantages and disadvantages. Multiple Inheritance surely offers more flexibility in designing, but languages which don't support Multiple Inheritance has different ways of simulating Multiple Inheritance if required to do so.
Single Inheritance | Multiple Inheritance | |
---|---|---|
Flexibility | Single Inheritance does have some restriction in terms of modeling real world objects, but mitigate this problem with careful design | Multiple Inheritance provides lots more flexibility for designing software |
Easy of Use | Single Inheritance is much simpler to use | Multiple Inheritance sometimes leaves the programmer confused. One of the common problem with Multiple inheritance in Diamond Problem |
Availability | Single Inheritance is supported by all the Object Oriented Programming Languages | Multiple Inheritance is not supported by few of the OOP Languages, because the designer of the language thought it makes the languauge complicated to use and not necessary for most scenarios. |
Efficiency | This actually language specific. But generally it takes less time to figure which version of the function to call in a single inheritance | Virtual Function dispatch in C++ is more time consuming in Multiple Inheritance. For example Multiple Inheritance in C++, needs multiple vtable for dispatching virtual functions. (1) discusses about this problem in great detail. |