- SalesforceChaCha
- Posts
- 💃 Stop Extending, Start Assembling 🕺
💃 Stop Extending, Start Assembling 🕺
Prefer Composition over Inheritance
Good morning, Salesforce Nerds! Apex class hierarchy feel more like a family drama than a clean architecture? You’re not alone.
Inheritance can feel like OOP’s golden child. 🥇
Define a base class, extend it, override what you need. Done, right?
But in real-world Apex projects, that "simple" hierarchy can snowball into rigid, tangled class chains that are brittle and hard to test. 🔗
Today we’re talking about composition over inheritance, a design principle that favors assembling behavior using component objects instead of relying on class hierarchies.
You keep flexibility, reduce coupling, and stop turning your codebase into a game of Jenga. 📦️

TABLE OF CONTENTS
💃 Stop Extending, Start Assembling 🕺
WHY COMPOSITION JUST WORKS
SO, WHAT’S THE BIG DEAL?
Let’s break it down. 👇️
Inheritance expresses an "is-a" relationship:
public virtual class Animal {
public virtual void speak() {
System.debug('Some sound');
}
}
public class Dog extends Animal {
public override void speak() {
System.debug('Woof!');
}
}
Dog
is-an Animal
. Great. 👍️
But what if your Dog
needs logging, feature toggles, or notification logic?
You’ll end up either bloating the parent class or creating Frankenstein hybrids.
Composition, on the other hand, expresses a "has-a" relationship. 🤝
A Dog
has a BarkBehavior
, a LogStrategy
, a FeedScheduler
.
In the end. You build small, testable, swappable parts: 😋
public interface IBarkBehavior {
void bark();
}
public class LoudBark implements IBarkBehavior {
public void bark() {
System.debug('WOOF!');
}
}
public class Dog {
private IBarkBehavior barkBehavior;
public Dog(IBarkBehavior barkBehavior) {
this.barkBehavior = barkBehavior;
}
public void bark() {
barkBehavior.bark();
}
}
Now your Dog can bark differently at runtime. 🐶
No class explosion. 💥
No deep inheritance chain. ⛓️
Just clean separation of concerns. ✨
THE “TOO MUCH REUSE” TRAP
WHEN INHERITANCE BREAKS BAD
Inheritance is tempting when you want code reuse. 🤔
But it comes with baggage:
🖇️ Tight Coupling: Child classes rely on superclass implementation details.
💔 Fragility: Changes to the base class ripple downstream.
🥵 Hierarchy Hell: Deep class trees that are hard to reason about.
🧪 Testing Pain: Mocking or stubbing behavior requires setup gymnastics.
Any of this sound familiar in your Apex codebase?
Think of all those extends BaseService
classes that override 5 methods just to change one small behavior. 😰
Or the AbstractHandler
pyramid that’s become too rigid to maintain. 😱
With composition, you can:
🔌 Plug in collaborators
🔄 Swap behavior at runtime
🔍️ Keep classes laser-focused
As a bonus, composition plays nicely with Dependency Injection (DI) and the Strategy Pattern, both of which rely on interfaces and delegation. 💯
BREAK THINGS INTO BEHAVIOR
APEX IN THE COMPOSITION ZONE
Let’s say you’re building a Lead assignment engine. 🚂
You start with:
public virtual class LeadAssigner {
public virtual void assignLead(Lead l) {
// default assignment logic
}
}
Then come RoundRobinAssigner
, GeoAssigner
, ProductLineAssigner
, and suddenly you’re drowning in a brittle inheritance tree. 🌲
Now try composition:
public interface ILeadAssignmentStrategy {
void assign(Lead l);
}
public class RoundRobinStrategy implements ILeadAssignmentStrategy {
public void assign(Lead l) {
// round robin logic
}
}
public class AssignmentService {
private ILeadAssignmentStrategy strategy;
public AssignmentService(ILeadAssignmentStrategy strategy) {
this.strategy = strategy;
}
public void execute(Lead l) {
strategy.assign(l);
}
}
Want to swap out logic? Just pass in a different strategy. 🔥
You’ve created flexibility without extending anything.
ESCAPE THE ABSTRACT BASE TRAP
REFACTOR THAT MONSTER HIERARCHY
Let’s say you inherited (pun intended) a legacy codebase with 10 subclasses of AbstractEmailSender
.
Each one overrides send()
just slightly. 📧
You’re afraid to touch anything.
Any of this sound familiar? If so, try this as a refactor strategy: 📜
Identify what's varying between subclasses.
Extract each variation into an interface (
IEmailSendStrategy
,ITemplateProvider
, etc.)Use constructor injection to compose those behaviors into a new
EmailService
.
Before:
public abstract class AbstractEmailSender {
public virtual void send(String to) {
/*...*/
}
}
After:
public interface IEmailSendStrategy {
void send(String to);
}
public class MarketingEmailStrategy implements IEmailSendStrategy {
public void send(String to) {
/* logic */
}
}
public class EmailService {
private IEmailSendStrategy strategy;
public EmailService(IEmailSendStrategy strategy) {
this.strategy = strategy;
}
public void execute(String to) {
strategy.send(to);
}
}
This gives you:
Isolated, testable strategies 👌
Zero inheritance 0️⃣
Pluggable runtime behavior 💪
Cleaner code for future devs (including you) 🧹
REUSE WITH LESS REGRET
WRAP IT BEFORE YOU EXTEND IT
Composition over inheritance isn’t about banning extends
forever. ❌
There are still good use cases for inheritance in Apex (like custom exceptions or extending base test factories).
But when you find yourself reaching for inheritance just to share logic or inject varying behavior. Pause. 🛑
Ask yourself:
“Can I delegate this instead?” 🤔
Start wrapping behaviors. Break classes into smaller parts. Favor interfaces and injection.
Your Apex codebase will become more modular, testable, and adaptable to change.
This is the way. 👈️
SOUL FOOD
Today’s Principle
"The hardest part of design is keeping features out."
and now....Salesforce Memes



What did you think about today's newsletter? |