Polymorphism
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 Count for Everyone
Imagine counting everyone in a school building. You could write separate logic for teachers, students, and administrators. Polymorphism offers a different approach: treat each person as a SchoolMember and use one general operation for everyone. The code does not need to know which specific subtype it received.
Polymorphism allows a subtype to be treated as an instance of its parent type. This enables flexible code that works with multiple subtypes without checking each object's specific type.
Runtime Method Selection
Polymorphism becomes especially useful when a parent class defines an operation that subtypes can provide in their own way. Suppose SchoolMember has a method named tell(), and Teacher and Student are subtypes. Code can work with a SchoolMember reference and call tell() without first checking whether the object is a Teacher or a Student.
Dynamic dispatch determines which method runs at runtime from the object's actual subtype. If a variable is treated as a SchoolMember but the object it refers to is a Teacher, calling tell() runs the Teacher version of tell(), not the generic SchoolMember version. The reference type provides the parent-level view, while the actual object type determines the method implementation that executes.
Following the Object
Choosing the Correct tell() Method
A variable is treated as a SchoolMember, but the object assigned to it is a Teacher. What happens when tell() is called?
Start with the parent view: The variable is treated as a SchoolMember, so general code can work with it through the parent type.
Identify the actual object: The object held by the variable is a Teacher, which is a subtype of SchoolMember.
Dispatch at runtime: Dynamic dispatch uses the actual object type to select the method implementation.
Run the subtype method: The Teacher version of tell() runs rather than the generic SchoolMember version.
A parent-typed reference can lead to execution of the correct subtype method because runtime dispatch follows the actual object type.
The important separation is between how the object is treated and what the object really is. The parent type gives code a common way to work with different objects. Dynamic dispatch then preserves subtype-specific behavior when the operation is executed.
Inherited Functionality
Inheritance also makes shared functionality available across the subtype family. When functionality is added to the parent class, all of its subtypes automatically receive that functionality. For example, if a new ID card field is added to SchoolMember, both Teacher and Student objects have access to that field without either subtype needing a separate modification.
Isolated Subtype Changes
The direction of a change matters. A functionality change made in SchoolMember propagates to its subtypes. A change made only in Teacher remains in Teacher; it does not alter Student or the SchoolMember parent. This isolation allows one subtype to gain specialized behavior without changing the other members of the subtype family.
When reasoning about a change, ask where it was made. A parent-class change is shared by all subtypes. A subtype-only change stays localized to that subtype and does not affect its siblings or its parent.
Mistakes with Polymorphism
Checking every object's specific subtype before using a shared operation
Polymorphism is intended to let code work with multiple subtypes through their parent type without type checking.
Fix:
Use the common parent type for the shared operation and let runtime dispatch select the correct subtype method.Assuming the reference type determines the method that executes
Dynamic dispatch selects the method at runtime from the actual object type.
Fix:
Distinguish the parent type used by the code from the actual subtype of the object.Assuming a subtype change spreads to sibling subtypes
Changes made in a subtype remain isolated and do not affect other subtypes or the parent class.
Fix:
Place functionality in the parent when it should be shared, and place it in a subtype when it should remain specialized.
Extension without Rewriting
Polymorphism supports the Open-Closed Principle. Code can be open for extension because new subtypes can be added, while existing code can remain closed for modification because it already works through the parent type. The benefit is a uniform operation for a growing set of subtypes rather than a separate type-specific branch for every subtype.
A school program needs to process both Teacher and Student objects using one general SchoolMember-based operation. Explain how polymorphism helps the program avoid checking each object's specific subtype, and describe which method should run when the actual object is a Teacher.
Hints
- Start by identifying the parent type shared by both objects.
- Separate the type used by the general code from the object's actual subtype.
- Use dynamic dispatch to determine which subtype implementation runs.
Polymorphism in Practice
- Polymorphism lets a subtype be treated as its parent type, so one piece of code can work with multiple subtypes uniformly. Dynamic dispatch chooses the correct subtype method at runtime from the actual object type. Functionality added to a parent class becomes available to all subtypes, while functionality added only to one subtype remains isolated. Together, these properties let code support new subtypes without requiring existing code to be modified for every specific type.
Key Takeaways
- A subtype can be treated as an instance of its parent type.
- Dynamic dispatch selects the correct subtype method at runtime based on the actual object type.
- Functionality added to a parent class automatically becomes available to its subtypes.
- Changes made in one subtype remain isolated from the parent and sibling subtypes.
- Polymorphism supports flexible code that can be extended with new subtypes without changing existing general logic.