Skip to main content

Sign in to CleanKata

Track your progress, earn XP, and unlock every lesson.

By signing in you agree to our Terms of Use and Privacy Policy.

Clean Architecture80 XP8 min

Polymorphism: The Power of Plugins

Polymorphism lets you treat external dependencies — database, UI, I/O — as interchangeable plugins, making core logic device-independent.

Why this matters

Before OOP, calling a function required knowing its address at compile time — or using a pointer, which was error-prone and undisciplined. OOP's contribution to architecture is not encapsulation or inheritance: it is safe, disciplined polymorphism through interfaces. An interface makes function pointers implicit, typed, and safe.

This is architecturally transformative. When you depend on an interface rather than a concrete class, the direction of the source-code dependency becomes independent of the direction of control flow. You can point any dependency at an abstraction and swap implementations freely — making external systems (databases, APIs, UI frameworks, I/O devices) into plugins to your core logic. The main module decides which plugin to load. The core never knows and never cares.

✗The problem

Dispatching on a type string with if/elif forces every new output format to edit the same function. Adding "slack" or "webhook" means touching core logic — violating the Open-Closed Principle.

Bad

class ReportGenerator:
    def send(self, report: dict, output_type: str) -> None:
        if output_type == "pdf":
            print(f"[PDF] {report['title']}")
        elif output_type == "csv":
            print(f"[CSV] {report['title']}")
        elif output_type == "email":
            print(f"[EMAIL] {report['title']}")
        else:
            raise ValueError(f"Unknown: {output_type}")
class ReportGenerator {
  send(report: { title: string }, outputType: string): void {
    if (outputType === "pdf")        console.log(`[PDF] ${report.title}`);
    else if (outputType === "csv")   console.log(`[CSV] ${report.title}`);
    else if (outputType === "email") console.log(`[EMAIL] ${report.title}`);
    else throw new Error(`Unknown: ${outputType}`);
  }
}

✓The solution

An abstract notifier interface makes each output format a plugin. The core generator never changes — only a new class is added when a new format is needed.

Good

from abc import ABC, abstractmethod

class ReportNotifier(ABC):
    @abstractmethod
    def send(self, report: dict) -> None: ...

class PdfNotifier(ReportNotifier):
    def send(self, r): print(f"[PDF] {r['title']}")

class CsvNotifier(ReportNotifier):
    def send(self, r): print(f"[CSV] {r['title']}")

class EmailNotifier(ReportNotifier):
    def send(self, r): print(f"[EMAIL] {r['title']}")

class ReportGenerator:
    def __init__(self, notifier: ReportNotifier):
        self._notifier = notifier

    def send(self, report: dict) -> None:
        self._notifier.send(report)

gen = ReportGenerator(PdfNotifier())
gen.send({"title": "Q1 Sales"})
interface ReportNotifier {
  send(report: { title: string }): void;
}

class PdfNotifier   implements ReportNotifier {
  send(r: { title: string }) { console.log(`[PDF] ${r.title}`); }
}
class CsvNotifier   implements ReportNotifier {
  send(r: { title: string }) { console.log(`[CSV] ${r.title}`); }
}
class EmailNotifier implements ReportNotifier {
  send(r: { title: string }) { console.log(`[EMAIL] ${r.title}`); }
}

class ReportGenerator {
  constructor(private notifier: ReportNotifier) {}
  send(report: { title: string }): void { this.notifier.send(report); }
}

const gen = new ReportGenerator(new PdfNotifier());
gen.send({ title: "Q1 Sales" });

💡Key takeaway

Polymorphism's architectural superpower is not inheritance — it is the ability to make any external system a swappable plugin, leaving core business logic permanently unchanged.

🔧 Some exercises may still have errors. If something seems wrong, use the Feedback button (bottom-right of the page) to report it — it helps us fix it fast.

Hint: Every if/else on a type tag is a missed opportunity for polymorphism.

✗ Your version

Polymorphism: The Power of Plugins — CleanKata — CleanKata