Inheritance is an Object Oriented Programming technique that allows for code reuse. An object can share a common set of features with other objects. Take for example the object Shape. All shapes will have a name and a method to calculate the area, called getArea. These common attributes and methods would be defined in an object known as the base class. In our example this is the Shape object. Other objects like a Circle, Square or Triangle would be descendants of the base class Shape. This principle defines a relationship that is known in OOP as a “is-a relationship“; the Circle is a Shape.
Abstract Data Types
The Shape object is a kind of Abstract Data Type (ADT). This is because the method “getArea” would be calculated for each object differently. The interface and signature for the method “getArea” for the Circle, Square and Triangle would remain the same, however the implementation for each would differ. In C++ this is achieved by denoting in the base class that the method is virtual. Since the “Shape” object itself has no knowledge of how to calculate the area for a random undefined shape it would have no implementation. This means in C++ the method in the Shape object interface would be declared as a pure virtual method.
Before I mentioned that every shape shares the attribute “name“. This brings us up a design issue. Should we add a variable to the base class called “name” or rather a pure virtual method called “getName” which is then overridden in each derived classes. By not using a variable we would reduce the memory foot print of our object, but in some languages this could make debugging difficult as you would not be able to see the name of the object, which can be critical to solving some bugs. Experience has led me to believe a hybrid of this approach is best. Using preprocessor flags one can easily have a variable that is defined for the name attribute that only exists in the Debug build of your application. This way you have the ability to debug while reducing the memory foot print in the Release build.
Circle – Ellipse Problem
Inheritance while when used correctly can save time and create beautifully crafted code but it can also expose poor design choices. One commonly known problem is the is the “Circle-Ellipse” problem. A Circle is an Ellipse. If we model this relationship in code then we would encounter a problem. An Ellipse object could provide methods to alter the axis of Ellipse. These methods would allow the x-axis and y-axis to be different. If a Circle would inherit this behavior it could violate the basic requirement that a Circle must have the same x and y axis stretch. There are many ways to solve this problem, one of which is NOT to make the Ellipse derive itself from the Circle. This would add undefined and extra behavior to an Ellipse (i.e. Think of methods to work with the radius of a Circle, this makes no sense for an Ellipse). Possible solutions would be to add return value to methods, throwing exceptions or my favorite decomposing the common functionality of a Circle and Ellipse into an base object called RoundShape.