Designing Class Hierarchies
A parent-class change is shared by all subtypes because subtypes inherit the parent's attributes and methods. This makes shared functionality easier to add and maintain.
One Collection, Many Roles
Imagine counting everyone in a school building. You could write separate logic for teachers, students, and administrators. Polymorphism offers a different design: treat each person as a SchoolMember while allowing the object to retain the behavior of its specific subtype. The shared code does not need to check whether it received a Teacher or a Student.
Polymorphism allows a subtype to be treated as an instance of its parent type. A Teacher or Student can therefore be handled as a SchoolMember while still using the behavior belonging to the actual subtype.
Runtime Method Selection
The parent-type view of an object does not erase the object's subtype. When a method such as tell() is called, dynamic dispatch examines the object's actual runtime type and chooses the matching method. If a SchoolMember reference holds a Teacher object, the Teacher version of tell() runs. If that reference holds a Student object, the Student version runs. The choice is based on the real object, not merely on the parent type used to refer to it.
What do you think happens?
A variable is treated as a SchoolMember, but the object it refers to is a Teacher. Which version of tell() executes?
Reveal answer
The Teacher version
Dynamic dispatch uses the actual runtime type of the object. The parent-type reference allows the object to be handled as a SchoolMember, but the Teacher object's behavior remains available.
Parent Features Everywhere
Inheritance creates a downward flow of functionality. When a new attribute or method is added to SchoolMember, every subtype inherits it automatically. For example, adding an ID card field to SchoolMember makes that field available to both Teacher and Student without changing either subtype.
A parent-class change is shared by all subtypes because subtypes inherit the parent's attributes and methods. This makes shared functionality easier to add and maintain.
Contained Subtype Changes
Inheritance also provides isolation in the opposite direction. If a method is added only to Teacher, that method does not appear in Student and does not alter SchoolMember. Changing one subtype changes the behavior of that subtype without accidentally changing its siblings or its parent.
A Flexible Design Boundary
Polymorphism lets one operation work with multiple subtypes through their shared parent type. Instead of writing separate functions for Teacher and Student, code can work with SchoolMember objects and call the shared operation. Dynamic dispatch supplies the appropriate subtype behavior at runtime.
This design supports the Open-Closed Principle. Existing code can remain closed for modification while the class hierarchy stays open for extension: a new subtype can be added, and existing code that works with the parent type can continue to work without being rewritten for every subtype.
| Design action | Effect |
|---|---|
| Add functionality to SchoolMember | The functionality becomes available to Teacher and Student |
| Add functionality only to Teacher | Teacher changes; Student and SchoolMember remain unaffected |
| Process objects as SchoolMember instances | The same code can work with multiple subtypes |
| Call tell() through a parent-type reference | Dynamic dispatch selects the actual subtype's version |
How inheritance and polymorphism distribute behavior
Mistakes in Hierarchy Design
Common mistake Assuming the parent-type reference determines the method that runs
What it looks likeTreating a Teacher object referenced as SchoolMember as if only the generic SchoolMember behavior could execute
Why it failsDynamic dispatch selects the method using the object's actual runtime type.
Do this insteadDetermine which subtype object is actually being held; its matching method is selected at runtime.
Common mistake Expecting a subtype-only change to spread to sibling subtypes
What it looks likeAdding a method to Teacher and expecting Student to receive it
Why it failsChanges made within a subtype remain isolated from its siblings and parent.
Do this insteadAdd shared functionality to SchoolMember when all subtypes should receive it.
Common mistake Writing separate logic for every known subtype
What it looks likeCounting teachers and students separately when both can be treated as SchoolMember objects
Why it failsThis gives up the flexibility of polymorphism and makes the shared operation depend on subtype checks.
Do this insteadUse the shared parent type when the operation applies uniformly to multiple subtypes.
Common mistake Forgetting that inherited functionality is available automatically
What it looks likeModifying both Teacher and Student after adding an ID card field to SchoolMember
Why it failsSubtypes inherit the parent's attributes and methods.
Do this insteadPlace shared functionality in the parent and allow inheritance to propagate it.
Practice the Reasoning
A SchoolMember hierarchy contains Teacher and Student. First, predict the result of these changes: an ID card field is added to SchoolMember; a new method is added only to Teacher; and tell() is called on a SchoolMember reference that holds a Student object. State which objects receive each change and which tell() implementation executes.
Hints
- Changes in the parent class propagate downward to its subtypes.
- Changes in one subtype do not propagate sideways to sibling subtypes.
- Dynamic dispatch uses the actual runtime type of the object.
Tracing Three Hierarchy Changes
Determine the effect of three operations in a SchoolMember hierarchy: add an ID card field to SchoolMember, add a method only to Teacher, and call tell() through a SchoolMember reference holding a Student object.
- 1Parent change
The ID card field is added to SchoolMember, so both Teacher and Student inherit access to it.
- 2Subtype change
The new method is added only to Teacher, so it remains isolated to Teacher and does not alter Student or SchoolMember.
- 3Runtime call
The reference is parent-typed, but the actual object is Student. Dynamic dispatch therefore selects Student's version of
tell().
The ID card field is available to Teacher and Student; the new method exists only in Teacher; and Student.tell() executes for the Student object.
Key Takeaways
- 1Polymorphism allows a subtype such as Teacher or Student to be treated as its parent type, SchoolMember.
- 2Dynamic dispatch chooses the correct subtype method at runtime based on the actual object type.
- 3Functionality added to the parent class automatically becomes available to its subtypes.
- 4Functionality added only to one subtype remains isolated from the parent and sibling subtypes.
- 5Polymorphism supports flexible code that can be extended with new subtypes without modifying existing shared logic.
At a glance
- Polymorphism lets code treat different subtype objects uniformly through a shared parent type.
- Dynamic dispatch preserves subtype-specific behavior when a method is called.
- Parent-class functionality propagates to all subtypes through inheritance.
- Subtype-specific changes remain isolated from the parent and sibling subtypes.
- This combination supports extensible code under the Open-Closed Principle.