- 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? |