Concepts / Abstract Base Classes and Interfaces

Abstract Base Classes and Interfaces

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

is ais aaccepted asTeacherschool memberSchoolMemberparent typeStudentschool membercount membersone shared process
How can objects from different subtypes be passed to the same piece of code and treated uniformly as instances of their parent type?

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?

Shared treatment: The surrounding operation handles both objects as SchoolMember instances. It does not need separate logic for Teacher and Student.

First runtime type: For the Teacher object, dynamic dispatch selects Teacher's version of tell().

Second runtime type: For the Student object, dynamic dispatch selects Student's version of tell().

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

each objectruntime type Teacherruntime type StudentSchoolMemberobjectsTeacher and Studentcall tell()same operationTeacher.tell()Teacher behaviorStudent.tell()Student behavior
How does control flow remain the same when a collection contains different subtypes, while each object still provides its own behavior?

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.

parent ofparent ofSchoolMemberID card fieldTeacherinherits ID cardStudentinherits ID card
When functionality is added to a parent class, how does that functionality become available to all of its subtypes?

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.

parent ofparent ofparent ofparent ofSchoolMembershared behaviorTeacherexisting behaviorSchoolMembershared behaviorTeachernew methodStudentexisting behaviorStudentexisting behavior
What changes when behavior is added or modified in one subtype, and what remains unchanged in the parent class and sibling subtypes?

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.

  • Assuming the parent type determines which overridden method runs.

    Dynamic dispatch selects the method using the actual runtime type of the object.

    Fix: Track the object itself. A Teacher object causes Teacher's version of tell() to run, even when accessed through a SchoolMember reference.

  • Writing separate processing logic for every subtype when the operation only needs the parent type.

    This gives up the uniform processing that polymorphism provides.

    Fix: Write one operation around the parent type and let each subtype provide its own runtime behavior.

  • Expecting a subtype-only method to appear in sibling subtypes.

    Subtype changes remain isolated.

    Fix: Add shared functionality to the parent when every subtype should receive it; keep specialized functionality in the subtype that owns it.

  • Expecting a parent-class change to affect only one subtype.

    Functionality added to the parent automatically propagates to all subtypes.

    Fix: Place functionality in the parent only when it should be shared by its subtypes.

Practice the Prediction

MEDIUM

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.

Teacher-only method: The new method belongs to Teacher, so it is available to Teacher and is not automatically added to Student or SchoolMember.

Shared operation: The collection can still be processed through the SchoolMember type because both objects are SchoolMember subtypes.

Teacher behavior: Calling tell() for the Teacher object selects Teacher's version through dynamic dispatch.

Student 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

  1. A subtype can be treated as an instance of its parent type, allowing one operation to work with multiple subtypes.
  2. Dynamic dispatch chooses the correct subtype method at runtime based on the actual object type, not the parent type of the reference.
  3. Functionality added to a parent class becomes available to all of its subtypes.
  4. A change made only in one subtype remains isolated from the parent class and sibling subtypes.
  5. Polymorphism 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.