Abstract Base Classes and Interfaces
Inheritance does not make every class change flow in every direction.
One Collection, Many Roles
Imagine counting everyone in a school building. One approach is to count teachers, students, and administrators separately. A polymorphic approach treats each person as a SchoolMember, so one piece of code can process them together without knowing each person's specific role. The objects still retain their specialized behavior, but the surrounding code can work with the shared parent type.
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 which specific subtype each object belongs to.
The Runtime Method Choice
A parent-type reference can point to an object created from a subtype. When a method is called through that reference, dynamic dispatch chooses the method according to the object's actual runtime type, not according to the parent type used by the reference. If a SchoolMember reference holds a Teacher object, calling tell() runs the Teacher version of tell(). If the reference holds a Student object, the Student version runs.
Calling tell() Through a Parent Type
A collection is treated as a collection of SchoolMember objects, but it contains one Teacher and one Student. What happens when the same tell() operation is applied to each object?
- 1Shared treatment
The surrounding operation handles both objects as SchoolMember instances. It does not need separate logic for Teacher and Student.
- 2First runtime type
For the Teacher object, dynamic dispatch selects Teacher's version of
tell(). - 3Second runtime type
For the Student object, dynamic dispatch selects Student's version of
tell(). - 4Control flow
The operation remains the same even though the behavior supplied by each object is different.
One parent-oriented operation can invoke the correct subtype behavior for each object at runtime.
Inheritance as Shared Functionality
Inheritance creates a second important effect: functionality added to a parent class automatically becomes available to all of its subtypes. For example, if an ID card field is added to SchoolMember, both Teacher and Student objects gain access to that field without either subtype needing to be changed.
A subtype object contains the parent's data along with any additional data defined by the subtype. This layered structure explains why parent functionality can operate on a subtype object: the parent portion of that object is present as part of the subtype object.
Changes That Stay Local
Inheritance does not make every class change flow in every direction. A change made to the parent is available to its subtypes, but a change made only to one subtype remains isolated. If a new method is added to Teacher, that method does not automatically appear in Student or in SchoolMember. This isolation lets you specialize one subtype without accidentally changing sibling subtypes or the parent.
Designing for Extension
Polymorphism allows one function or operation to work with the parent type rather than requiring a separate version for every subtype. When a new subtype is added, existing code can continue to work without modification, provided the new subtype can be treated as the parent type. This supports the Open-Closed Principle: code is open for extension because new subtypes can be added, and closed for modification because existing processing code does not need to change.
Suppose a school-reporting operation processes SchoolMember objects. It can accept teachers and students through the same parent-oriented operation. Adding another SchoolMember subtype later does not require the reporting operation to add a new type-specific branch. The new subtype supplies its own behavior through dynamic dispatch while the surrounding operation remains unchanged.
Common mistake Assuming the parent type determines which overridden method runs.
What it looks likeA SchoolMember reference holds a Teacher object, but the learner expects the generic SchoolMember version of tell() to run.
Why it failsDynamic dispatch selects the method using the actual runtime type of the object.
Do this insteadTrack the object itself. A Teacher object causes Teacher's version of tell() to run, even when accessed through a SchoolMember reference.
Common mistake Writing separate processing logic for every subtype when the operation only needs the parent type.
What it looks likeCounting teachers and students in separate paths even though both can be treated as SchoolMember objects.
Why it failsThis gives up the uniform processing that polymorphism provides.
Do this insteadWrite one operation around the parent type and let each subtype provide its own runtime behavior.
Common mistake Expecting a subtype-only method to appear in sibling subtypes.
What it looks likeAdding a method to Teacher and then expecting Student to have that method.
Why it failsSubtype changes remain isolated.
Do this insteadAdd shared functionality to the parent when every subtype should receive it; keep specialized functionality in the subtype that owns it.
Common mistake Expecting a parent-class change to affect only one subtype.
What it looks likeAdding an ID card field to SchoolMember while expecting it to be available only on Teacher.
Why it failsFunctionality added to the parent automatically propagates to all subtypes.
Do this insteadPlace functionality in the parent only when it should be shared by its subtypes.
Practice the Prediction
A collection is processed as SchoolMember objects. It contains a Teacher and a Student. The Teacher subtype receives a new method that is not added to SchoolMember. Predict which classes can use that new method and what happens when the shared tell() operation is called for each object.
Hints
- Separate the effect of a subtype-only change from the effect of dynamic dispatch.
- Ask which class owns the new method.
- For
tell(), identify the actual runtime type of each object.
Checking the Prediction
Determine the effect of adding a Teacher-only method while processing a Teacher and a Student through the SchoolMember type.
- 1Teacher-only method
The new method belongs to Teacher, so it is available to Teacher and is not automatically added to Student or SchoolMember.
- 2Shared operation
The collection can still be processed through the SchoolMember type because both objects are SchoolMember subtypes.
- 3Teacher behavior
Calling
tell()for the Teacher object selects Teacher's version through dynamic dispatch. - 4Student behavior
Calling
tell()for the Student object selects Student's version through dynamic dispatch.
The Teacher-only method remains isolated, while the shared tell() operation continues to use each object's correct subtype behavior.
Polymorphism in Practice
- 1A subtype can be treated as an instance of its parent type, allowing one operation to work with multiple subtypes.
- 2Dynamic dispatch chooses the correct subtype method at runtime based on the actual object type, not the parent type of the reference.
- 3Functionality added to a parent class becomes available to all of its subtypes.
- 4A change made only in one subtype remains isolated from the parent class and sibling subtypes.
- 5Polymorphism supports code that is open for extension and closed for modification.
Key Takeaways
- Polymorphism lets code treat Teacher, Student, and other subtypes uniformly as SchoolMember objects.
- Dynamic dispatch ensures that each object supplies the correct subtype implementation at runtime.
- Parent functionality propagates to subtypes, while subtype-specific changes remain local.
- Uniform parent-oriented processing reduces type-specific logic and supports the Open-Closed Principle.