Concepts / Method Overriding in Subclasses

Method Overriding in Subclasses

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.

  • Programming

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.

parent type ofparent type ofcan containcan containSchoolMemberTeacherStudentSchoolMembercollection
How can objects from different subclasses be treated uniformly as instances of their shared parent type?

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.

refers toreceivesTeacher objectStudent objectSchoolMemberreferenceActual objecttell()Teacher tell()Student tell()
When code calls a method through a parent-type reference, how does control reach the correct subclass implementation?

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?

Gather the objects: Place Teacher and Student objects in a collection viewed as containing SchoolMember references.

Use the shared type: The processing logic handles each object as a SchoolMember instead of branching on whether it is a Teacher or a Student.

Call shared behavior: When the logic calls tell(), dynamic dispatch examines the actual object and selects the matching subtype implementation.

Add 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.

processes as SchoolMemberprocesses as SchoolMemberdispatches todispatches toMember processorTeacherStudentTeacher behaviorStudent behavior
How does one operation process multiple subtypes through one parent-type interface without branching on each concrete type?

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.

inherited byinherited bySchoolMemberID card fieldTeacherID card fieldStudentID card field
What functionality is added to every subtype when a new method or field is added to the parent class?

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.

parent ofparent ofparent ofparent ofSchoolMemberoriginal behaviorTeacheroriginal behaviorSchoolMemberoriginal behaviorTeacheroverridden behaviorStudentoriginal behaviorStudentoriginal behavior
When one subtype overrides or changes a method, which classes and objects are affected, and which remain unchanged?

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

  • Assuming the parent-type reference determines which method runs.

    Dynamic dispatch selects the method from the actual runtime object type, so the Teacher implementation runs.

    Fix: Separate the reference type from the actual object type when reasoning about an overridden method call.

  • Assuming a method added to one subtype becomes available in every subtype.

    Changes made in a subtype remain isolated and do not affect sibling subtypes or the parent class.

    Fix: Place functionality in the parent when every subtype should inherit it; place it in one subtype when it should remain specialized.

  • Writing separate processing logic for every known subtype.

    Polymorphism is intended to let one operation work with multiple subtypes through their shared parent type without type checking.

    Fix: Use the parent type for the shared operation and allow dynamic dispatch to select subtype-specific behavior.

  • Assuming a parent-class change must be repeated manually in every subtype.

    Functionality added to the parent class automatically propagates to its subtypes.

    Fix: Reason from the inheritance relationship: inherited parent attributes and methods become available to the subtypes.

Practice the Dispatch

MEDIUM

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?

  • The SchoolMember implementation because the reference uses the parent type
  • The Teacher implementation because the actual object is a Teacher
  • No implementation because a subtype cannot be treated as its parent
Reveal answer

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

  1. Polymorphism lets a subtype be treated as an instance of its parent type.
  2. Dynamic dispatch chooses the overridden method at runtime from the actual object type.
  3. Functionality added to a parent class automatically becomes available to its subtypes.
  4. Changes made in one subtype remain isolated from sibling subtypes and the parent class.
  5. Parent-type processing supports flexible code that can accept multiple subtypes without type checking.

Key Takeaways

  • 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.