Method Overriding in Subclasses
The method that executes is determined at runtime from the actual object type, not from the parent type used to refer to the object.
One Group, Many Roles
Imagine counting everyone in a school building. You could write separate logic for teachers, students, and administrators. Polymorphism offers another approach: treat each person as a SchoolMember while allowing each subtype to keep its specialized behavior. The code works with the shared parent type instead of needing to know every concrete subtype.
Polymorphism allows a subtype to be treated as an instance of its parent type. This enables flexible code that works with multiple subtypes without type checking.
The Parent-Type View
A subtype is a specialized form of its parent type. A Teacher object can therefore be used anywhere a SchoolMember is expected, and a Student object can be treated in the same general way. The code using the object does not need to care which specific subtype it received. It can rely on the behavior and functionality associated with the parent type.
Polymorphism separates what the calling code needs to know from how each subtype performs its behavior. The calling code uses the shared parent-type interface; the actual object supplies the specialized implementation.
This makes one piece of code reusable across multiple subtypes. Instead of creating separate functions for teachers and students, you can write one operation that works with SchoolMember objects. When a new subtype is added, that existing operation can continue working without modification.
Finding the Overridden Method
Suppose SchoolMember provides a tell() method, and Teacher and Student each provide their own version. A variable can be treated as a SchoolMember reference while actually holding a Teacher object. When tell() is called, the Teacher version runs. If the object is a Student, the Student version runs instead.
This selection is dynamic dispatch. The method that executes is determined at runtime from the actual object type, not from the parent type used to refer to the object. The decision happens each time the method is called, so one collection of SchoolMember references can contain different subtypes and still invoke the correct implementation for each object.
A SchoolMember Collection
Counting and Notifying School Members
A school needs to process teachers and students together. How can it use one parent-type view while preserving each subtype's behavior?
- 1Gather the objects
Place Teacher and Student objects in a collection viewed as containing SchoolMember references.
- 2Use the shared type
The processing logic handles each object as a SchoolMember instead of branching on whether it is a Teacher or a Student.
- 3Call shared behavior
When the logic calls
tell(), dynamic dispatch examines the actual object and selects the matching subtype implementation. - 4Add another subtype
A new SchoolMember subtype can participate in the same processing approach without requiring the existing processing logic to change.
One operation can work with multiple SchoolMember subtypes uniformly, while each object retains its own implementation of overridden behavior.
The important design choice is that the processing logic depends on SchoolMember rather than on a list of concrete subtype checks. The objects are handled through the same parent-type view, but dynamic dispatch preserves the correct behavior for each actual object.
Inherited Functionality
Inheritance works in the other direction when functionality is added to the parent. If a new ID card field is added to SchoolMember, both Teacher and Student objects automatically have access to it. The same propagation applies to new parent-class methods. Subtypes inherit the parent class's attributes and methods without requiring each subtype to be modified separately.
Isolated Subtype Changes
Changes made inside one subtype remain isolated. If a new method is added only to Teacher, that method does not appear in Student and does not modify SchoolMember. Likewise, changing how Teacher behaves does not change the behavior of Student or the parent class.
Subtype isolation makes maintenance safer: changing one subtype's behavior does not accidentally change the parent class or the behavior of sibling subtypes.
Mistakes About Overriding
Common mistake Assuming the parent-type reference determines which method runs.
What it looks likeA SchoolMember reference holds a Teacher object, and the caller expects the generic SchoolMember implementation to run.
Why it failsDynamic dispatch selects the method from the actual runtime object type, so the Teacher implementation runs.
Do this insteadSeparate the reference type from the actual object type when reasoning about an overridden method call.
Common mistake Assuming a method added to one subtype becomes available in every subtype.
What it looks likeA method added only to Teacher is expected to appear in Student.
Why it failsChanges made in a subtype remain isolated and do not affect sibling subtypes or the parent class.
Do this insteadPlace functionality in the parent when every subtype should inherit it; place it in one subtype when it should remain specialized.
Common mistake Writing separate processing logic for every known subtype.
What it looks likeA school-processing operation handles Teacher objects and Student objects through separate branches even though both are SchoolMembers.
Why it failsPolymorphism is intended to let one operation work with multiple subtypes through their shared parent type without type checking.
Do this insteadUse the parent type for the shared operation and allow dynamic dispatch to select subtype-specific behavior.
Common mistake Assuming a parent-class change must be repeated manually in every subtype.
What it looks likeAn ID card field is added to SchoolMember, but each subtype is treated as though it must define the field again.
Why it failsFunctionality added to the parent class automatically propagates to its subtypes.
Do this insteadReason from the inheritance relationship: inherited parent attributes and methods become available to the subtypes.
Practice the Dispatch
A collection is viewed as containing SchoolMember objects. It contains one Teacher and one Student, and both subtypes provide their own tell() implementation. Describe what happens when the same processing operation calls tell() for each object. Then explain what would happen if a new field were added to SchoolMember and a separate method were added only to Teacher.
Hints
- Track the actual object type for each collection item.
- Remember that dynamic dispatch chooses the implementation at runtime.
- Distinguish functionality added to the parent from functionality added to one subtype.
What do you think happens?
A parent-type reference holds a Teacher object. Which tell() implementation does a call through that reference use?
Reveal answer
The Teacher implementation because the actual object is a Teacher.
Dynamic dispatch selects the correct subtype method at runtime based on the actual object type, not the reference type.
Designing for Extension
Polymorphism supports the Open-Closed Principle. Code using the parent type is open for extension because new subtypes can be added, but closed for modification because the existing code does not need to change for each new subtype. This design also improves maintainability: subtype-specific behavior can be changed in the subtype while the parent class and other subtypes remain unaffected.
Key Takeaways
- 1Polymorphism lets a subtype be treated as an instance of its parent type.
- 2Dynamic dispatch chooses the overridden method at runtime from the actual object type.
- 3Functionality added to a parent class automatically becomes available to its subtypes.
- 4Changes made in one subtype remain isolated from sibling subtypes and the parent class.
- 5Parent-type processing supports flexible code that can accept multiple subtypes without type checking.
At a glance
- Polymorphism allows objects of different subtypes to be handled through a shared parent-type view.
- Dynamic dispatch sends a method call to the correct subtype implementation at runtime.
- Parent-class functionality propagates automatically to all subtypes.
- Subtype-specific changes remain isolated and do not alter sibling subtypes or the parent.
- This combination enables flexible code that is open for extension and closed for modification.