DevLearningTools

MODULE 7 · LESSON 11

Polymorphism

How components sharing a parent (or an interface) can respond to the exact same method call in their own way — the payoff that makes an inheritance hierarchy actually useful.

New lessons are added one at a time as the course gets built out — a graded quiz for each lesson is still on the way.

Polymorphism means calling the exact same method name on different objects and getting behavior specific to each one — the caller doesn't need to know or care which concrete type it's actually dealing with.

The previous lesson's Dog.describe() overriding Animal.describe() was already an example of this in miniature. This lesson is about the actual payoff: writing code that works against a whole family of related objects at once.

Learning Objectives

After completing this lesson, you'll be able to:

  • Write several components that share a parent and override the same method differently.
  • Loop over a mixed collection of those objects and call the shared method on each.
  • Explain why this matters more as a hierarchy grows.

One Method Name, Different Behavior

CFScript — Shape.cfc
component {
    function area() {
        return 0;
    }
}
CFScript — Circle.cfc
component extends="Shape" {
    property name="radius" type="numeric";

    function init(required numeric radius) {
        variables.radius = arguments.radius;
        return this;
    }

    function area() {
        return 3.14159 * variables.radius * variables.radius;
    }
}
CFScript — Rectangle.cfc
component extends="Shape" {
    property name="width" type="numeric";
    property name="height" type="numeric";

    function init(required numeric width, required numeric height) {
        variables.width = arguments.width;
        variables.height = arguments.height;
        return this;
    }

    function area() {
        return variables.width * variables.height;
    }
}
Tag Syntax — Shape.cfc
<cfcomponent>
    <cffunction name="area" returnType="numeric">
        <cfreturn 0>
    </cffunction>
</cfcomponent>
Tag Syntax — Circle.cfc
<cfcomponent extends="Shape">
    <cfproperty name="radius" type="numeric">

    <cffunction name="init">
        <cfargument name="radius" type="numeric" required="true">
        <cfset variables.radius = arguments.radius>
        <cfreturn this>
    </cffunction>

    <cffunction name="area" returnType="numeric">
        <cfreturn 3.14159 * variables.radius * variables.radius>
    </cffunction>
</cfcomponent>
Tag Syntax — Rectangle.cfc
<cfcomponent extends="Shape">
    <cfproperty name="width" type="numeric">
    <cfproperty name="height" type="numeric">

    <cffunction name="init">
        <cfargument name="width" type="numeric" required="true">
        <cfargument name="height" type="numeric" required="true">
        <cfset variables.width = arguments.width>
        <cfset variables.height = arguments.height>
        <cfreturn this>
    </cffunction>

    <cffunction name="area" returnType="numeric">
        <cfreturn variables.width * variables.height>
    </cffunction>
</cfcomponent>

The Payoff: One Loop, Many Shapes

CFScript
Simulated output — illustrative only, not a live ColdFusion/Lucee server.
Tag Syntax
Simulated output — illustrative only, not a live ColdFusion/Lucee server.
NOTE

The loop calls shape.area() without ever checking "is this a Circle or a Rectangle?" — each object knows how to compute its own area. Adding a Triangle.cfc later wouldn't require changing this loop at all.

Common Beginner Mistakes

Checking the object's type before calling a method it already overrides correctly

if (isInstanceOf(shape, "Circle")) { ... } else if (...) { ... } defeats the point — that branching logic is exactly what polymorphism replaces. Just call shape.area() and let each object handle its own case.

Forgetting a shared method exists on the parent, so it can't be called polymorphically

For a loop like the one above to work on any Shape, area() has to be declared on Shape itself (even as a stub returning 0), so every subclass is guaranteed to have it.

Best Practices

  • Design the parent's method signatures first, then build subclasses that fulfill them — polymorphism only works cleanly when every subclass honors the same method name and shape.
  • Avoid type-checking branches (isInstanceOf, an if/else chain) where a polymorphic method call would do the same job more simply.

Interview Questions

What does polymorphism let you do that a plain if/else type check doesn't?

Call the same method name on objects of different concrete types and get each one's own correct behavior automatically, without the calling code needing to know which specific type it's dealing with.

Why does a shared method usually need to exist on the parent component for polymorphism to work in a loop?

So every subclass is guaranteed to implement it — calling code that loops over a mixed collection can then call that method safely on any item, without checking its concrete type first.

Summary

In this lesson, you built a small hierarchy of shapes and looped over a mix of them, calling the same method on each and letting each object supply its own behavior — the real payoff of inheritance.

What's Next?

The next lesson covers abstraction — hiding a complex operation behind a simple, named method, plus ColdFusion's abstract keyword for components that are never meant to be instantiated directly.