๐Ÿ’ƒ 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

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: ๐Ÿ“œ 

  1. Identify what's varying between subclasses.

  2. Extract each variation into an interface (IEmailSendStrategy, ITemplateProvider, etc.)

  3. 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."

Donald Norman

and now....Salesforce Memes

What did you think about today's newsletter?

Login or Subscribe to participate in polls.