Object Design Inheritance and Composition
An introduction to object design classes and interfaces, including their roles in software design.
There are only two types of relationships in an object model: composition and inheritance. Both have analogs in a family tree. A composition relation is like a marriage between objects. It is dynamic, it happens during the participating objects' lifetimes, and it can change. Objects can discard partners and get new partners to collaborate with. Inheritance relations are more like births into the family. Once it happens, it is forever. Just as both marriage and ancestry appear in the same family tree, composition and inheritance coexist in a single object model.
A family tree describes the structural relations of a group of people. Someone is added to the tree in one of two ways: by marriage or by birth.
We can extend an object's capabilities by composing it from others. When it lacks the features that it needs to fulfill one of its responsibilities, we simply delegate the responsibility for the required information or action to one of the objects that the object holds onto. This is a very flexible scenario for extension. As the program continues execution, it plugs components together, dynamically, according to the conditions of the application.
For objects to communicate, they must know about each other for the duration of their collaborations. Composition is one way to create those paths of communication. Passing a helper object along with a request, or creating a new instance, are two other ways that an object gains visibility of potential collaborations.
Inheritance is another way to extend an object's capabilities. But whereas composition is dynamic, inheritance isn't. It's static. The merging of the superclass responsibilities and the extension of its subclasses are done at compile time and not run time. Objects are not plugged together; instead, the descriptions used to compile them (the classes) are.
An instance uses another's responsibilities through collaboration. An instance assumes another's responsibilities through inheritance.
Every inheritance relationship between two classes involves two roles: the superclass role and the subclass role. Each acts out its role during development. With few exceptions, a subclass assumes all of the responsibilities outlined in the superclass and adds new responsibilities of its own. The subclass inherits all of the features encoded in the superclass and has the responsibility for instantiating objects having those features. The subclass extends the superclass. In this arrangement, the superclass contains features that are common to all of its subclasses, and each subclass not only creates its own instances but also adds features to them that are not described in the superclass. A subclass extends the superclass by adding attributes and operations. An instance's responsibilities are the union of all of the responsibilities in its own class and all of the responsibilities of the superclasses that it inherits from.
It's common to say that a subclass "specializes" its superclass because the added responsibilities make the subclass's role less general than that of the superclass.
Classes sometimes relinquish their responsibility for producing instances to their subclasses. These abstract classes define many of the features of instances, but they require subclasses to fill in some details and to do the actual manufacturing.
Inheritance relations demonstrate the Peter Principle. The higher in a hierarchy a class resides, the less capable of really doing anything it becomes.