The Definition Is Technically Correct And Useless
Go looking for a definition of encapsulation and you’ll find something like:
“Encapsulation refers to bundling data with the methods that operate on that data, and restricting direct access to some of an object’s components.”
Which is correct. It’s also so general that it’s hard to do anything with unless you’ve got an example in front of you.
And here’s my actual complaint: almost everywhere leads with the “bundling data with methods” half, and that’s not the interesting part. Bundling is just… what a class is. You did that the moment you wrote your first class.
The real wonder of encapsulation is the second half: restricting direct access. Hiding things. That’s where the value lives, so that’s what we’ll focus on.
If you haven’t seen the previous episode on OOP, encapsulation is one of its four pillars and it’s worth having that context first.
The Problem With Leaving Everything Open
public class BankAccount
{
public Decimal balance;
}Simple. Also completely defenceless:
BankAccount acct = new BankAccount();
acct.balance = -50000; //sure, why not
acct.balance = null; //also fine apparentlyNothing stops any code anywhere in your org from putting that object into a nonsensical state. And when you find a negative balance in production, every single line of code that touches this class is a suspect, because any of them could have done it.
Encapsulating It
public class BankAccount
{
//Nobody outside can touch this directly
private Decimal balance;
public BankAccount(Decimal startingBalance)
{
this.balance = (startingBalance == null || startingBalance < 0)
? 0
: startingBalance;
}
public void deposit(Decimal amount)
{
if (amount == null || amount <= 0)
{
throw new IllegalArgumentException('Deposit must be positive');
}
this.balance += amount;
}
public void withdraw(Decimal amount)
{
if (amount == null || amount <= 0)
{
throw new IllegalArgumentException('Withdrawal must be positive');
}
if (amount > this.balance)
{
throw new IllegalArgumentException('Insufficient funds');
}
this.balance -= amount;
}
public Decimal getBalance()
{
return this.balance;
}
}Now the balance can only change through deposit() and withdraw(), and both of those enforce the rules.
The genuinely valuable consequence: it is now impossible for a BankAccount to have a negative balance. Not “unlikely.” Impossible. There is no code path that produces one.
And if a bug did appear, you’d have exactly two methods to inspect instead of your whole codebase. That’s the payoff.
Getters, Setters, And Not Missing The Point
Apex properties give you a tidier syntax:
public class BankAccount
{
public Decimal balance { get; private set; }
}Readable from anywhere, writable only from inside. Nice.
But here’s the trap, and a lot of code falls into it:
//This is NOT encapsulation. This is a public field wearing a disguise.
public class BankAccount
{
private Decimal balance;
public Decimal getBalance() { return balance; }
public void setBalance(Decimal b) { balance = b; }
}Anyone can still set the balance to anything. You’ve added two methods and zero protection.
Encapsulation is not “make fields private and add getters and setters.” It’s “decide deliberately what the outside world is allowed to do, then only allow that.” Sometimes that means no setter at all. A setter that validates nothing is just a public field with extra typing.
Hiding Implementation, Not Just Data
Encapsulation covers your methods too:
public class OpportunityService
{
//The only thing anyone else calls
public void processOpportunity(Id oppId)
{
Opportunity opp = fetchOpportunity(oppId);
applyDiscount(opp);
update opp;
}
//Internal machinery. Change these freely.
private Opportunity fetchOpportunity(Id oppId) { ... }
private void applyDiscount(Opportunity opp) { ... }
}One public door, private internals. You can rewrite applyDiscount() completely tomorrow and know for certain nothing else breaks, because nothing else can call it.
That freedom to change is the thing you’re actually buying, and it’s the same argument I made in the private keyword episode.
A Salesforce Flavoured Example
public class OpportunityWrapper
{
private Opportunity opp;
public OpportunityWrapper(Opportunity opportunity)
{
this.opp = opportunity;
}
public Boolean isClosedWon()
{
return opp.StageName == 'Closed Won';
}
public Boolean isHighValue()
{
return opp.Amount != null && opp.Amount > 100000;
}
}Now “what counts as high value” lives in exactly one place. When Finance decides the threshold is £250,000, you change one line rather than hunting for Amount > 100000 across forty classes.
Same for the stage name. If somebody renames that picklist value, you’ve got one place to fix rather than a search-and-pray exercise.
The Practical Rule
Start everything private. Open it up only when something genuinely needs it.
Before you make anything public, ask what happens if somebody sets it to something absurd. If the answer is “nothing good,” it needs a method with rules in it rather than an open field.
Encapsulation isn’t about hiding things from your colleagues. It’s about making the wrong thing impossible so nobody has to remember not to do it.
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