SOLID Design Principles In Salesforce Master Class

The Liskov Substitution Principle in Apex (SOLID Ep. 4)

4 min read

Subscribe on YouTube

The One With The Scary Name

Right, the Liskov Substitution Principle. Named after Barbara Liskov, who introduced it in 1987. Here’s the formal definition, and I want you to read it purely so you can appreciate how unhelpful it is:

“If S is a subtype of T, then objects of type T may be replaced with objects of type S without altering any of the desirable properties of the program.”

Cool. Thanks.

Here’s what it actually means:

If you swap a subclass in where the parent class was expected, nothing should break.

Or even more bluntly: don’t write subclasses that lie about what they do.


The Classic Example

Every explanation of LSP uses rectangles and squares, and honestly it’s used constantly because it’s genuinely the clearest illustration. A square is a rectangle, mathematically. So this seems obviously correct:

Apex
public virtual class Rectangle
{
    protected Decimal width;
    protected Decimal height;

    public virtual void setWidth(Decimal w)  { width = w; }
    public virtual void setHeight(Decimal h) { height = h; }

    public Decimal getArea() { return width * height; }
}

public class Square extends Rectangle
{
    //A square's sides must match, so keep them in sync
    public override void setWidth(Decimal w)  { width = w; height = w; }
    public override void setHeight(Decimal h) { width = h; height = h; }
}

Looks sensible. Now here’s a method that works perfectly with a Rectangle:

Apex
public void resizeAndCheck(Rectangle rect)
{
    rect.setWidth(5);
    rect.setHeight(4);

    //Obviously 20, right?
    System.debug('Area: ' + rect.getArea());
}

Pass in a Rectangle: area is 20. Correct.

Pass in a Square: setWidth(5) sets both to 5, then setHeight(4) sets both to 4. Area is 16.

The method didn’t change. The caller did nothing wrong. But swapping in the subclass broke the behaviour, and nothing anywhere reported an error. It just silently returned the wrong number.

That’s an LSP violation. Square claims to be a Rectangle but doesn’t honour the contract that width and height are independent.


A Version You’ll Actually Meet

Rectangles are fine for illustration, but here’s the shape this takes in a real Salesforce org:

Apex
public virtual class RecordSaver
{
    public virtual void save(List<SObject> records)
    {
        insert records;
    }
}

public class ReadOnlyRecordSaver extends RecordSaver
{
    public override void save(List<SObject> records)
    {
        //Nope. We don't save here.
        throw new UnsupportedOperationException('This saver is read only');
    }
}

Any code holding a RecordSaver reasonably expects save() to save something. Hand it a ReadOnlyRecordSaver and it explodes.

Now every caller has to defensively check what it’s actually got:

Code
//If you're writing this, LSP has already been violated
if (!(saver instanceof ReadOnlyRecordSaver))
{
    saver.save(records);
}

And that’s the tell. If your calling code has to check which subclass it received, your inheritance hierarchy is wrong. The entire point of polymorphism is that the caller doesn’t have to care.


The Three Ways Subclasses Lie

1. Throwing exceptions the parent doesn’t throw. The read only saver above.

2. Tightening the input requirements. If the parent accepts any List and your subclass secretly requires a non-empty one, you’ve broken the contract.

Apex
public class StrictSaver extends RecordSaver
{
    public override void save(List<SObject> records)
    {
        //Parent was happy with an empty list. This isn't.
        if (records.isEmpty())
        {
            throw new IllegalArgumentException('Must have records');
        }
        insert records;
    }
}

3. Weakening what you promise to return. If the parent guarantees a non-null List and your subclass sometimes returns null, every caller now needs a null check it didn’t need before.

The rule of thumb: a subclass can accept more and promise more, but never less of either.


How To Fix It

Usually the honest answer is that the inheritance was wrong in the first place. A read only saver isn’t a kind of saver. A square isn’t the kind of rectangle that method needed.

Split the contract instead:

Apex
public interface RecordReader
{
    List<SObject> read(Set<Id> recordIds);
}

public interface RecordWriter
{
    void save(List<SObject> records);
}

Now a class implements whichever it can genuinely honour, and nothing has to pretend. Code that needs to write asks for a RecordWriter and gets one that actually writes.

That’s a neat handoff, because splitting fat contracts into focused ones is precisely the Interface Segregation Principle, which is next.

Two other useful moves:

Prefer composition over inheritance. If Square simply has a side length instead of extending Rectangle, the whole problem evaporates. Inheritance is a strong claim; make it only when it’s genuinely true.

Make the parent abstract if there’s no sensible default. Don’t provide an implementation subclasses will need to sabotage.


The Practical Test

You don’t need the formal definition. Just ask:

“If I swap in this subclass, does any existing caller need to change or start checking types?”

If yes, you’ve violated LSP.

Or the test-flavoured version, which I like even better: every test that passes for the parent class should pass for every subclass, unchanged. If you’re writing special case tests for a subclass, that’s your answer.


What’s Next

Liskov: subclasses must be usable anywhere the parent is, without callers knowing or caring which they’ve got.

In the next episode, the Interface Segregation Principle, and why one giant interface is worse than four small ones.

See you next time!


Get Coding With The Force Merch!!

We now have a redbubble store setup so you can buy cool Coding With The Force merchandise! Please check it out! Every purchase goes to supporting the blog and YouTube channel.

Get Shirts Here!
Get Cups, Artwork, Coffee Cups, Bags, Masks and more here!


Check Out More Coding With The Force Stuff!

If you liked this post make sure to follow us on all our social media outlets to stay as up to date as possible with everything!

Youtube
Patreon
Github
Facebook
Twitter
Instagram


Salesforce Development Books I Recommend

Advanced Apex Programming
Salesforce Lightning Platform Enterprise Architecture
Mastering Salesforce DevOps

Good Non-SF Specific Development Books:

Clean Code
Clean Architecture