- SalesforceChaCha
- Posts
- 💃 Refactor Like a Rockstar 🕺
💃 Refactor Like a Rockstar 🕺
Replacing Conditionals with Polymorphism in Apex
Good morning, Salesforce Nerds! Every seasoned Salesforce developer knows the feeling …
You inherit an Apex class that looks more like a logic labyrinth than business automation. 😖
You scroll past an endless stack of if/else if/else
blocks that try to dictate behavior based on record type, status, profile, or (heaven forbid) hardcoded strings.
At first, you think, “I’ll just add one more condition.” 🤷
But soon, your codebase becomes a monument to indecision.
So today, we’re trading conditional chaos for clean, object-oriented calm. 🧘
Let’s talk about the refactoring pattern called Replace Conditional with Polymorphism, and why it might just be your new favorite trick.
Let’s get it. 👇️

TABLE OF CONTENTS
💃 Refactor Like a Rockstar 🕺
NOW WITH 40% MORE ELSE IF
THE CONDITIONAL JUNGLE
What we’re trying to avoid is methods with so many branches it should be managed by the U.S. Forestry Service. 🌲
These if/else
blocks start simple but grow into a brittle mess as requirements change and business logic multiplies.
And they always do. 💯
It’s time to admit that your giant switch
statement isn’t “just fine” … it’s a code smell begging for an object-oriented air freshener.
You know the smell. 🦨 A method with branching logic like:
if(opportunity.StageName == 'Prospecting') {
doProspectingThings();
} else if(opportunity.StageName == 'Proposal/Price Quote') {
doProposalThings();
} else if(opportunity.StageName == 'Closed Won') {
doVictoryDance();
} else {
log('Stage not handled!');
}
This works … until it doesn’t. As business logic changes, these conditionals bloat and become:
😰 Hard to maintain: Adding new logic means touching existing code. Bad dev!
💔 Easy to break: One misplaced bracket and it’s NullReferenceException
O’Clock.
🔗 Tightly coupled: Your logic knows way too much about everything else.
This is the classic case where polymorphism can shine. 🌟
Instead of asking,
“What type of thing is this, so I can decide what to do?” ❌
you ask,
“What kind of thing is this, so it can decide what to do?” ✅
OOP TO THE RESCUE
ENTER POLYMORPHISM, STAGE LEFT
In Apex (and most object-oriented languages), polymorphism lets you treat different classes through a common interface or superclass. 👍️
Each class handles behavior its own way. No more switch-case babysitting. 🍼
Here's the refactor blueprint:
Define an interface or abstract class for your behavior.
Create a concrete class for each variation.
Use a factory method or dependency injection (bonus points for Custom Metadata!) to instantiate the correct class.
Let each class handle its own logic.
❌ Old Way (Conditionals Everywhere):
public void handleOpportunity(Opportunity opp) {
if (opp.StageName == 'Prospecting') {
System.debug('Logging early interest');
} else if (opp.StageName == 'Proposal/Price Quote') {
System.debug('Sending quote');
} else if (opp.StageName == 'Closed Won') {
System.debug('Celebrating');
}
}
💡 New Way (Polymorphism FTW):
public interface IOpportunityHandler {
void handle(Opportunity opp);
}
public class ProspectingHandler implements IOpportunityHandler {
public void handle(Opportunity opp) {
System.debug('Logging early interest');
}
}
public class ProposalHandler implements IOpportunityHandler {
public void handle(Opportunity opp) {
System.debug('Sending quote');
}
}
public class ClosedWonHandler implements IOpportunityHandler {
public void handle(Opportunity opp) {
System.debug('Celebrating');
}
}
Then wire it up with a factory:
public class OpportunityHandlerFactory {
public static OpportunityHandler getHandler(Opportunity opp) {
switch on opp.StageName {
when 'Prospecting'
return new ProspectingHandler();
when 'Proposal/Price Quote'
return new ProposalHandler();
when 'Closed Won'
return new ClosedWonHandler();
when else
throw new AuraHandledException('No handler found');
}
}
}
Now, your clients can use it like so:
OpportunityHandler handler = OpportunityHandlerFactory.getHandler(myOpp);
handler.handle(myOpp);
Clean. ✨
BYE, BRANCHING. HELLO, ELEGANCE.
APEX IN ACTION
Talk is cheap, especially in Apex. Let’s see what this pattern looks like in a real Salesforce org. 😎
We’ll walk through a classic scenario: different behaviors triggered based on a RecordType
.
You’ll see how cleanly this approach scales as logic branches multiply and how it makes test coverage less of a nightmare. 😱
We’re implementing this biz logic:
New Customer: Triggers product recommendation engine.
Existing Customer Upsell: Checks for existing licenses and usage.
Renewal: Validates pricing tiers and contract length.
This is just the beginning, remember how requirements change and grow. 👀
Old code might do this in a 150-line method riddled with if (opp.RecordTypeId == X)
checks. 🫠
But with polymorphism, you encapsulate behavior per RecordType
:
public interface IOpportunityStrategy {
void execute(Opportunity opp);
}
public class NewCustomerStrategy implements IOpportunityStrategy {
public void execute(Opportunity opp) {
ProductRecommender.recommend(opp);
}
}
public class UpsellStrategy implements IOpportunityStrategy {
public void execute(Opportunity opp) {
LicenseService.checkExisting(opp.AccountId);
}
}
public class RenewalStrategy implements IOpportunityStrategy {
public void execute(Opportunity opp) {
PricingValidator.validate(opp);
}
}
Now, each logic path is decoupled and testable. 😋
Future enhancements? Just add a new strategy class. No changes to the existing ones.
SNIFF ONCE. REFACTOR TWICE.
PERKS, PITFALLS, AND PASSING THE SNIFF TEST
Refactoring isn’t just about elegance. It’s about survivability. 👈️
Let’s break down the benefits you’ll gain, the situations where polymorphism is pure gold, and where it might be overkill.
If you want cleaner, testable, low-maintenance Apex, this is where the ROI becomes obvious.
✅ Benefits:
Open/Closed Principle: Add new behaviors without editing existing code.
Testable Units: Each class has one job, making unit tests a breeze.
Readable & Maintainable: Logic is modular and named for clarity.
⚠️ Gotchas:
Slightly more upfront setup: More files, more moving parts.
Overkill for trivial logic: Don’t polymorph everything. Start where complexity warrants it. Resist over-engineering a simple
if/then
orswitch
statement.Factory management: You'll want a clean strategy for object creation (consider Custom Metadata + Service Locator Pattern for larger implementations).
If you're thinking, “Is this really worth it?” 🤔
Ask yourself: Would you rather debug a switch
statement with 11 cases and nested ifs, or find the exact class handling that logic in one click?
I know my answer. 😅
YOUR CODE DESERVES BETTER
THE IF-LESS FUTURE IS NOW
At the end of the day, we’re talking about writing resilient code. 💪
The kind that survives the next round of business changes, or the junior dev who decides to “just add one more check.”
It’s a pattern that pays off in maintainability, clarity, and peace of mind. ☮️
Polymorphism gives you the structure to scale your code, and your career, without burning out or burning down the repo.
So go forth, refactor boldly, and may your switch
statements rest in peace. 🪦
SOUL FOOD
Today’s Principle
"Refactoring is about saying, ‘Let's restructure this system in order to make it easier to change it.’"
and now....Salesforce Memes



What did you think about today's newsletter? |