From grimoire
Refactors type-checking conditionals (if/switch on type) into polymorphic dispatch. Applies when behavior varies by type and new variants require editing existing code.
How this skill is triggered — by the user, by Claude, or both
Slash command
/grimoire:apply-polymorphismThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
When behavior varies by type, assign the responsibility for that variation to the types themselves using polymorphic operations — not to a caller that checks the type.
When behavior varies by type, assign the responsibility for that variation to the types themselves using polymorphic operations — not to a caller that checks the type.
Adopted by: Core OOP principle since Simula 67 (1967) and Smalltalk (1972); formalized in Gang of Four "Design Patterns" (1994) — 12 of the 23 patterns are applications of this principle (Strategy, Command, State, Visitor, Observer, etc.). Directly enables SOLID's Open/Closed Principle. Every modern OOP language enforces this via virtual methods, interfaces, and abstract classes.
Impact: Type-checking code (if type == "A", switch(type)) is a closed set: adding a new type always requires editing the conditional. Polymorphic code is an open set: adding a new type means adding a new class that implements the interface — existing code requires no edits. Google's internal refactoring guidelines cite conditional type checks as one of the top patterns targeted for polymorphic replacement.
Why best: Type-checking callers violate the Open/Closed Principle (SOLID O) and create coupling between the caller and every concrete type it checks. Polymorphism moves the type-specific behavior to the type itself — the only class that needs to change when a type's behavior changes is that type's class. This is the mechanism behind the Strategy, State, Command, and Template Method patterns.
Sources: Larman, "Applying UML and Patterns" (Prentice Hall, 2004); Gamma et al., "Design Patterns" (Addison-Wesley, 1994); Martin, "Agile Software Development" (Prentice Hall, 2003)
Any code that branches on an object's type is a candidate for polymorphic replacement.
# Type-checking smell — every new payment type requires editing this function
def process_payment(payment):
if payment.type == "credit_card":
stripe.charge(payment.amount, payment.card_number)
elif payment.type == "bank_transfer":
ach.transfer(payment.amount, payment.account_number)
elif payment.type == "crypto":
blockchain.send(payment.amount, payment.wallet)
# Adding "gift_card" requires editing this function
Create an interface (or abstract class) that declares the operation. Each type gets its own class that implements the operation for that type's behavior.
from abc import ABC, abstractmethod
class PaymentMethod(ABC):
@abstractmethod
def process(self, amount: float) -> Receipt: ...
class CreditCardPayment(PaymentMethod):
def process(self, amount):
return stripe.charge(amount, self.card_number)
class BankTransferPayment(PaymentMethod):
def process(self, amount):
return ach.transfer(amount, self.account_number)
class CryptoPayment(PaymentMethod):
def process(self, amount):
return blockchain.send(amount, self.wallet)
# Caller is closed to modification — no type checks
def process_payment(payment: PaymentMethod, amount: float):
return payment.process(amount)
For existing type-checking code, replace one branch at a time:
# Step 1: introduce interface, keep conditional for now
class PaymentMethod(ABC):
@abstractmethod
def process(self, amount): ...
# Step 2-3: migrate credit card, use polymorphic call for it
class CreditCardPayment(PaymentMethod):
def process(self, amount):
return stripe.charge(amount, self.card_number)
def process_payment(payment):
if isinstance(payment, CreditCardPayment): # now polymorphic for this type
return payment.process(payment.amount)
elif payment.type == "bank_transfer": # still transitional
ach.transfer(...)
# continue migrating...
When an object's behavior changes based on its internal state, use the State pattern — each state is a class implementing the state interface.
# Bad — Order behavior varies by status string; adding a status requires editing everywhere
class Order:
def can_cancel(self):
if self.status == "pending": return True
if self.status == "shipped": return False
if self.status == "delivered": return False
if self.status == "refunded": return False
# Good — each state encapsulates its own behavior
class OrderState(ABC):
@abstractmethod
def can_cancel(self) -> bool: ...
class PendingState(OrderState):
def can_cancel(self): return True
class ShippedState(OrderState):
def can_cancel(self): return False
class Order:
def can_cancel(self):
return self.state.can_cancel() # delegates to state; no type checking
The one acceptable place for type-based conditionals is at system entry points where raw data (JSON, form input) must be converted to typed domain objects. The conditional lives at the boundary; once inside the system, everything is polymorphic.
# Acceptable — type conditional at deserialization boundary only
def deserialize_payment(data: dict) -> PaymentMethod:
match data["type"]:
case "credit_card": return CreditCardPayment(**data)
case "bank_transfer": return BankTransferPayment(**data)
case "crypto": return CryptoPayment(**data)
case _: raise ValueError(f"Unknown type: {data['type']}")
# After this point: all code is polymorphic, no type checks
Using isinstance() as polymorphism. Adding isinstance(obj, ConcreteClass) guards before calling type-specific methods is still type-checking, not polymorphism. Every isinstance check is a candidate for a polymorphic interface.
Polymorphism for trivial variation. Not every boolean variation needs a class hierarchy. if is_debug: log() does not need a DebugMode class. Apply polymorphism when behavior is substantial and types are extensible.
Deep inheritance hierarchies. A polymorphic type hierarchy 4+ levels deep is harder to follow than a type check. Keep hierarchies shallow (1–2 levels). Prefer interfaces over inheritance for behavior variation.
True/False, enabled/disabled) and no third case is possible, a simple conditional is clearernpx claudepluginhub jeffreytse/grimoire --plugin grimoireGuides OOP polymorphism implementation using Java interfaces and abstract classes for extensible systems like interchangeable payment processors.
Use when an object's behavior changes based on its internal state and the logic for each state is complex — replacing large conditional chains with state objects where each state encapsulates its own behavior.
Covers 26 Gang of Four design patterns with PHP 8.3+ implementations, UML diagrams, and practical use cases.