Designing Class Hierarchies
Polymorphism allows a subtype to be treated as an instance of its parent type, enabling flexible code that works with multiple subtypes without type checking.
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
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 |
Mistakes in Hierarchy Design
Assuming the parent-type reference determines the method that runs
Dynamic dispatch selects the method using the object's actual runtime type.
Fix:
Determine which subtype object is actually being held; its matching method is selected at runtime.Expecting a subtype-only change to spread to sibling subtypes
Changes made within a subtype remain isolated from its siblings and parent.
Fix:
Add shared functionality to SchoolMember when all subtypes should receive it.Writing separate logic for every known subtype
This gives up the flexibility of polymorphism and makes the shared operation depend on subtype checks.
Fix:
Use the shared parent type when the operation applies uniformly to multiple subtypes.Forgetting that inherited functionality is available automatically
Subtypes inherit the parent's attributes and methods.
Fix:
Place 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.
Parent change: The ID card field is added to SchoolMember, so both Teacher and Student inherit access to it.
Subtype change: The new method is added only to Teacher, so it remains isolated to Teacher and does not alter Student or SchoolMember.
Runtime 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
- Polymorphism allows a subtype such as Teacher or Student to be treated as its parent type, SchoolMember.
- Dynamic dispatch chooses the correct subtype method at runtime based on the actual object type.
- Functionality added to the parent class automatically becomes available to its subtypes.
- Functionality added only to one subtype remains isolated from the parent and sibling subtypes.
- Polymorphism supports flexible code that can be extended with new subtypes without modifying existing shared logic.
Key Takeaways
- 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.