Why This Is Useful
An interface is a contract. It says “any class implementing me must have these methods.” No implementation, just the promise.
Which sounds abstract and pointless until you hit the problem it solves, so let’s start there.
Code’s on GitHub.
The Problem
Say you’re building a “clone this record” feature. It needs to work on Accounts and Contacts, and each one clones differently, because an Account has related Opportunities and a Contact doesn’t.
The obvious approach:
public void cloneRecord(Id recordId, String objectType)
{
if (objectType == 'Account')
{
//account specific cloning
}
else if (objectType == 'Contact')
{
//contact specific cloning
}
//...and another branch every time somebody adds an object
}Which works, and is exactly the growing if statement I complained about in the Open/Closed Principle post. Every new object means editing a class that already works.
The Interface
public interface Clone_Interface
{
void cloneRecord(Id recordId);
void cloneRelatedRecords(Id recordId);
}Note what’s missing: no bodies, no access modifiers on the methods, no { }. Interface methods are implicitly public and have no implementation. That’s the whole file.
Now each object gets its own implementation:
public with sharing class AccountCloneLogic implements Clone_Interface
{
public void cloneRecord(Id recordId)
{
System.debug('This is the account clone logic running');
}
public void cloneRelatedRecords(Id recordId)
{
System.debug('This is the account clone related records logic running');
}
}
public with sharing class ContactCloneLogic implements Clone_Interface
{
public void cloneRecord(Id recordId)
{
System.debug('This is the contact clone logic running');
}
public void cloneRelatedRecords(Id recordId)
{
System.debug('This is the contact clone related records logic running');
}
}The compiler now enforces the contract. Miss a method and it won’t save. That’s genuinely useful when somebody adds a fourth implementation eight months from now.
Using Them Interchangeably
public with sharing class CloneRecords_Service
{
public void doTheClone(Id recordId)
{
Clone_Interface cloner = getCloner(recordId);
cloner.cloneRecord(recordId);
cloner.cloneRelatedRecords(recordId);
}
private Clone_Interface getCloner(Id recordId)
{
String objectType = recordId.getSObjectType().getDescribe().getName();
switch on objectType
{
when 'Account' { return new AccountCloneLogic(); }
when 'Contact' { return new ContactCloneLogic(); }
when else
{
throw new IllegalArgumentException('No cloner for ' + objectType);
}
}
}
}Look at doTheClone. It has no idea what type it’s working with. It holds a Clone_Interface and calls methods on it. Whether that’s an Account cloner or a Contact cloner is somebody else’s problem.
That’s polymorphism, and it’s the actual point of interfaces. One variable type, many possible behaviours behind it.
(The getSObjectType() trick there is from this quick tip, if you haven’t seen it.)
Interfaces vs Abstract Classes
Reasonable question, since both let you define a shape.
An interface has no implementation at all, and a class can implement as many as it likes.
An abstract class can provide shared implementation, but a class can only extend one.
Rule of thumb: if your implementations share real code, use an abstract class. If they only share a shape, use an interface.
You can also do both. An abstract base class that implements an interface is a common and perfectly sensible pattern, and it’s roughly what a trigger framework does.
You’re Already Using Them
Salesforce hands you interfaces constantly:
public class MyJob implements Queueable, Database.AllowsCallouts { ... }
public class MyBatch implements Database.Batchable<SObject> { ... }
public class MyScheduled implements Schedulable { ... }
public class MyMock implements HttpCalloutMock { ... }Every one of those is a contract. Salesforce doesn’t know or care what your class does, only that it has the required methods so the platform can call them.
Note MyJob implements two at once. That’s the flexibility interfaces give you that inheritance doesn’t.
The Real Payoff: Testing
The biggest practical win is that interfaces give you a seam you can swap in tests:
@IsTest
private class CloneServiceTest
{
private class MockCloner implements Clone_Interface
{
public Boolean wasCalled = false;
public void cloneRecord(Id recordId) { wasCalled = true; }
public void cloneRelatedRecords(Id recordId) {}
}
//Hand that in and test the service without touching the database
}You cannot do that with a concrete class hardcoded into your service. This is the whole basis of the Dependency Inversion Principle, and it’s why the Apex Common Library is built on interfaces throughout.
When Not To Bother
If there’s exactly one implementation and there will only ever be one, an interface is a second file that buys you nothing except the ability to mock it. Which is sometimes reason enough, and often isn’t.
Reach for one when you have genuinely different implementations of the same idea, when you need to swap something out in tests, or when the platform requires it.
Full details in the Apex interfaces documentation.
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