I have never been quite comfortable with using inheritance liberally, and I don't subscribe to the theory that this feature is essential for software reuse. There are three related but distinct flavors of inheritance, and in this section, I'll list what I like or dislike about these aspects. The three types of inheritance are as follows:
The facility provided by a language for a subclass to inherit attributes from a base class or a structure is called attribute inheritance. While C++ and Java provide this facility, Perl doesn't. The onus is on the Perl programmer to figure out a way for a superclass and a subclass to agree on a common inheritable representation. For this reason, a hash table is a frequent choice, but not necessarily an economical one, as has been pointed out earlier.
My problem with attribute inheritance is that it introduces a tremendous amount of coupling between an inherited class and a derived class. A change in the way a base class is laid out has drastic consequences for the derived class. This is clearly a violation of encapsulation. C++ treats all attributes as private by default but then provides a keyword called "protected," whereby it makes them freely available to derived classes, while still hiding them from the general public. Bjarne Stroustrup, the creator of C++, regrets this in his excellent book The Design and Evolution of C++ :
One of my concerns about
protectedis exactly that it makes it too easy to use a common base the way one might sloppily have used global data....In retrospect, I think that
protectedis a case where "good arguments" and fashion overcame my better judgement and my rules of thumb for accepting new features.
A better option is to provide accessor methods and rely on interface inheritance. More on this soon.
Perl supports only this flavor of inheritance. Implementation inheritance, like attribute inheritance, forces base and inherited classes to have a common understanding of the layout of the object's attributes; attribute inheritance is almost always required in using implementation inheritance.
Subclassing is not easy, as Erich Gamma et al. say in Design Patterns :
Designing a subclass also requires an in-depth understanding of the parent class. For example, overriding one operation might require overriding another. An overridden operation might be required to call an inherited operation. And subclassing can lead to an explosion of classes, because you might have to introduce many new subclasses for even a simple extension.
They suggest using composition instead, a topic we will touch on shortly.
The set of publicly available methods defines an object's interface. A derived class can add to this interface by adding new methods. But whether it actually overrides a base class implementation is strictly a matter of implementation detail; from the user's point of view, it still offers the same methods.
The important thing about an interface is that it represents the contract between the user and the object. If two objects have identical interfaces, they can be interchangeably used. This substitutability aspect represents the most important feature a language or a set of components can provide.
I was once convinced about the need for implementation inheritance when I was writing some widgets for Xt/Motif (GUI frameworks for the X Windows platform). This framework goes to a great extent to provide single inheritance in C (both attribute and implementation), but the result isn't easy to work with. When C++ came along, I quickly became enthusiastic about a language that supported inheritance, and attempted to implement the widget set in C++. Then when John Ousterhout's Tk came along, I marveled at the ease of creating widgets, even though it was in C and provided all the features that Motif provides (and much more). The Tk architecture used composition, not inheritance. I have been suitably chastened.
The idea of composition is for an object to be composed out of other objects. That is, it forms a has-a or uses-a relationship with other classes, instead of an is-a relationship. Many examples in published literature glorify implementation inheritance, but these turn out to be far better (simpler and more readable) candidates for composition. Take this commonly illustrated example of a class called
Vice-President, inheriting from a class called
Manager, inheriting from a class called
Employee. It is true that a V.P. is-a Manager, who in turn is an Employee, so the case is made for attribute and implementation inheritance. But what happens when an employee is promoted? The object is forced to change its class - clearly, a terrible design. The better way to approach this issue is to realize that an employee plays one or more roles in a company (that of a manager, vice-president, or lead technical engineer), and when the employee is promoted, this role is merely updated. In other words, the Employee object uses the Role class, which for its part, captures everything to be known about that role, such as the job description, salary range, and prerequisites.
Composition is also called component-driven programming. The key to developing reusable software is to develop completely encapsulated components with well-defined and documented interfaces. Designing for inheritance has, in my experience, rarely yielded the benefit that the hype would suggest.
Perl provides the most crucial features required to create plug and play components: polymorphism and run-time binding. You can say $obj->draw(), and Perl calls the appropriate draw() method, depending on $obj's class. Since Perl is an untyped language, it makes this statement work for graphic shapes, guns, and lotteries. I value this feature much more than its support for implementation inheritance.