DEV Community

Cover image for Polymorphism: Eliminating Conditionals and Unlocking Extensibility
Walter Nascimento
Walter Nascimento

Posted on

Polymorphism: Eliminating Conditionals and Unlocking Extensibility

Most developers think polymorphism means:

“One interface, multiple implementations.”

That’s technically correct.

But architecturally incomplete.

Polymorphism is about this:

Replacing conditional logic with behavioral variation.

If your system grows with switch statements,
you’re not using polymorphism.

You’re fighting it.


A Brief Historical Context

Polymorphism was introduced to allow:

  • Message passing between objects
  • Dynamic behavior resolution
  • Runtime flexibility

Instead of asking:

if ($type === 'credit_card') { ... }
Enter fullscreen mode Exit fullscreen mode

You delegate behavior to the object itself.

That shift changes everything.


❌ The Conditional Explosion Problem

Imagine a pricing system.

final class DiscountCalculator
{
    public function calculate(string $type, float $amount): float
    {
        switch ($type) {
            case 'percentage':
                return $amount * 0.9;

            case 'fixed':
                return $amount - 20;

            case 'none':
                return $amount;

            default:
                throw new InvalidArgumentException('Invalid discount type');
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Problems:

  • Violates Open/Closed Principle
  • Every new discount modifies the class
  • High risk of regression
  • Business rules centralized in conditionals
  • Hard to test behaviors independently

This grows over time.

Switch becomes monster.


✅ Introducing Polymorphism

Define a contract.

interface DiscountStrategy
{
    public function apply(float $amount): float;
}
Enter fullscreen mode Exit fullscreen mode

Implement behaviors separately.

final class PercentageDiscount implements DiscountStrategy
{
    public function __construct(
        private float $percentage
    ) {}

    public function apply(float $amount): float
    {
        return $amount * (1 - $this->percentage);
    }
}
Enter fullscreen mode Exit fullscreen mode
final class FixedDiscount implements DiscountStrategy
{
    public function __construct(
        private float $amountToSubtract
    ) {}

    public function apply(float $amount): float
    {
        return $amount - $this->amountToSubtract;
    }
}
Enter fullscreen mode Exit fullscreen mode
final class NoDiscount implements DiscountStrategy
{
    public function apply(float $amount): float
    {
        return $amount;
    }
}
Enter fullscreen mode Exit fullscreen mode

Now the calculator becomes:

final class PriceCalculator
{
    public function __construct(
        private DiscountStrategy $discountStrategy
    ) {}

    public function calculate(float $amount): float
    {
        return $this->discountStrategy->apply($amount);
    }
}
Enter fullscreen mode Exit fullscreen mode

No conditionals.

Behavior varies by object.

That’s polymorphism.


Why This Is Architecturally Superior

Adding a new discount:

final class SeasonalDiscount implements DiscountStrategy
{
    public function apply(float $amount): float
    {
        return $amount * 0.8;
    }
}
Enter fullscreen mode Exit fullscreen mode

You don’t modify existing code.

You extend behavior safely.

That’s Open/Closed Principle in action.

Polymorphism enables it.


Runtime Polymorphism vs Structural Polymorphism

PHP supports runtime polymorphism through:

  • Interfaces
  • Abstract classes
  • Method overriding

Example:

function checkout(DiscountStrategy $discount, float $amount): float
{
    return $discount->apply($amount);
}
Enter fullscreen mode Exit fullscreen mode

Any implementation works.

Behavior is resolved at runtime.

No conditionals needed.


The Real Power: Removing Knowledge of Types

Conditional logic requires type awareness:

if ($payment instanceof StripePayment) { ... }
Enter fullscreen mode Exit fullscreen mode

Polymorphism removes that knowledge.

The system does not care about concrete type.

It only cares about behavior.

That reduces coupling dramatically.


Advanced Insight: Polymorphism and Domain Modeling

In rich domain models, polymorphism represents real-world behavior variation.

Example:

Instead of:

if ($userType === 'admin') { ... }
Enter fullscreen mode Exit fullscreen mode

Model roles:

interface UserRole
{
    public function canApprove(): bool;
}
Enter fullscreen mode Exit fullscreen mode
final class AdminRole implements UserRole
{
    public function canApprove(): bool
    {
        return true;
    }
}
Enter fullscreen mode Exit fullscreen mode
final class CustomerRole implements UserRole
{
    public function canApprove(): bool
    {
        return false;
    }
}
Enter fullscreen mode Exit fullscreen mode

Now:

final class User
{
    public function __construct(
        private UserRole $role
    ) {}

    public function canApprove(): bool
    {
        return $this->role->canApprove();
    }
}
Enter fullscreen mode Exit fullscreen mode

No role conditionals across the system.

Behavior lives where it belongs.


When Polymorphism Is Overused

Polymorphism becomes harmful when:

  • You create abstractions without variability
  • There is only ever one implementation
  • You abstract prematurely
  • You hide simple logic behind unnecessary indirection

Like abstraction, polymorphism should follow variation.

Not fear of change.


Polymorphism vs Inheritance

Inheritance enables polymorphism.

But inheritance is not required for polymorphism.

Interfaces + composition are often cleaner.

Modern systems favor:

  • Composition
  • Strategy pattern
  • Interface-driven design

Deep inheritance hierarchies are rarely necessary.


The Architectural Impact

Without polymorphism:

  • Conditionals multiply
  • Code becomes rigid
  • Modifications become risky
  • Extension requires modification

With polymorphism:

  • Behavior becomes pluggable
  • Extension becomes safe
  • Testing becomes simpler
  • Systems evolve without breaking

Final Insight

Polymorphism is not about multiple implementations.

It is about removing decision logic from central coordinators.

If encapsulation protects invariants,
if abstraction protects dependency direction,
if inheritance models specialization,

Polymorphism unlocks extensibility.

And extensibility is what keeps systems alive over time.


Thanks for reading!

If you have any questions, complaints or tips, you can leave them here in the comments. I will be happy to answer!
😊😊 See you! 😊😊


Support Me

Youtube - WalterNascimentoBarroso
Github - WalterNascimentoBarroso
Codepen - WalterNascimentoBarroso

Top comments (0)