
Apex Design Patterns
Design patterns have revolutionized software programming over the past few decades by providing proven development paradigms to common problems. They encode expertise so that developers don’t have to reinvent the wheel with each new application.
Salesforce Apex code development stands to gain tremendously by leveraging design patterns. However, most resources focus on high level pattern overviews without concrete implementations. In this post, we will explore some of the most useful GoF design patterns for Apex through detailed code examples you can instantly apply in your org.
Benefits of Using Design Patterns
Let’s first talk about why design patterns, in particular, allow building robust and scalable applications faster in Apex:
Reusability – Well-crafted design patterns can be reused across applications saving tons of development time.
Testability – Design patterns promote loose coupling allowing easier unit testing of components.
Maintenance – Design patterns encapsulate complexity making it easier to troubleshoot or update components.
Consistency – Consistent usage of design patterns across development teams improves efficiency.
While JavaScript frameworks provide batteries-included architectures like MVC, no such comprehensive framework exists (yet!) for Apex where we build from the ground up. In this environment, design patterns become even more critical as the building blocks for scale and longevity.
Overview of Key Design Pattern Types
Let’s explore some of the categories of design patterns that are most relevant for Apex development, with examples:
Creational Patterns – Deal with the creation of objects
Structural Patterns – Concerned with the structure managing different objects/classes
Behavioral Patterns – Help define communication between objects
Type of Apex Design Pattern
Service Layer Pattern
The Service Layer pattern separates business logic from our presentation layer and data access. It acts as middleware providing a clean API for other layers.
We define an interface:
public interface ContactService {
List<Contact> queryContacts(String lastName);
void updateContacts(List<Contact> contacts);
}
And an implementation with our core logic:
public class ContactServiceImpl implements ContactService {
public List<Contact> queryContacts(String lastName) {
return [SELECT Id, Name FROM Contact WHERE LastName = :lastName];
}
public void updateContacts(List<Contact> contacts) {
update contacts;
}
}
This keeps our business rules reusable in one place and not locked inside a specific Apex class tangled with UI or storage logic. Any component can consume these APIs.
Repository Pattern
The Repository pattern abstracts data persistence operations behind a common interface providing a buffer between business components and raw data access:
First we define an abstract Repository interface:
public interface ContactRepository {
List<Contact> findContacts(ContactUtils.Query query);
void persistContacts(List<Contact> contacts);
}
Then we implement it to add database logic:
public class ContactRepositoryImpl implements ContactRepository {
public List<Contact> findContacts(ContactUtils.Query query) {
return Database.query(query);
}
public void persistContacts(List<Contact> contacts) {
insert contacts;
}
}
Now we can swap out the actual storage mechanism without coupling client components to it or mixing data logic into our business rules.
Strategy Pattern
The Strategy pattern encapsulates interchangeable algorithms and behaviors as separate concrete classes behind a common interface allowing dynamic runtime switching between options.
First we define an algorithm interface:
public interface ValidationStrategy {
Boolean isValid(Lead lead);
}
Then we implement multiple versions:
public class EmailValidationStrategy implements ValidationStrategy {
public Boolean isValid(Lead lead) {
// Email validation logic
return lead.Email <> null;
}
}
public class PhoneValidationStrategy implements ValidationStrategy {
public Boolean isValid(Lead lead) {
// Phone validation logic
return lead.Phone <> null;
}
}
We can now design supporting classes to accept any ValidationStrategy implementation via the interface and perform dynamic validation without hardcoding logic:
public class LeadManager {
ValidationStrategy strategy;
public void setStrategy(ValidationStrategy strategy) {
this.strategy = strategy;
}
public void validate(Lead lead) {
if (!strategy.isValid(lead)) {
// Fail validation
}
}
}
This makes our classes highly customizable to any context.
Iterator Pattern
The Iterator pattern encapsulates collection traversal behind a simple interface to abstract underlying data sources. This is very useful for batch Apex processing of QueryLocator or Iterable types:
public interface ItemIterator {
Boolean hasNext();
SObject next();
}
public class ContactIterator implements ItemIterator {
private List<Contact> contacts;
Integer index = 0;
// Constructor
public Boolean hasNext() {
return index < contacts.size();
}
public Contact next() {
return contacts[index++];
}
}
Client code can now iterate cleanly without worrying about specifics of data structure:
ItemIterator iterator = new ContactIterator(contacts);
while(iterator.hasNext()) {
Contact contact = (Contact) iterator.next();
// Process contact
}
This separates concerns for robust and flexible traversal logic.
Proxy Pattern
The Proxy pattern creates wrapper classes that control access to an underlying object providing additional runtime logic like caching or security without dirtying up primary classes:
Let’s define our interface:
public interface ContactService {
void updateContacts(List<Contact> contacts);
}
We then create two implementations:
// Actual business logic service
public class ContactServiceImpl implements ContactService {
public void updateContacts(List<Contact> contacts) {
update contacts;
}
}
// Proxy wrapper service
public class CachingContactService implements ContactService {
private ContactServiceImpl realService;
private List<Contact> cache;
public void updateContacts(List<Contact> contacts) {
// Cache contacts
cache.addAll(contacts);
// Call actual service
if(realService == null) {
realService = new ContactServiceImpl();
}
realService.updateContacts(contacts);
}
}
Now we can pass around the proxy CachingContactService to efficiently add caching while keeping primary service completely focused on core update logic.
Observer Pattern
The Observer pattern establishes a publish-subscribe relationship between objects, pushing updates from publisher to subscriber classes automatically:
We define a common event interface:
public interface ContactEvent {
void notifyContactUpdated(Contact contact);
}
We then create publisher:
public class ContactServiceImpl implements ContactService {
private List<ContactEvent> observers = new List<ContactEvent>();
public void registerObserver(ContactEvent observer) {
observers.add(observer);
}
public void updateContact(Contact contact) {
update contact; // Perform update
for(ContactEvent observer : observers) {
observer.notifyContactUpdated(contact);
}
}
}
And subscribers:
public class EmailService implements ContactEvent {
// Handle contact updated notification
public void notifyContactUpdated(Contact contact) {
// Send email about update
}
}
This allows building highly decoupled systems that can grow without forcing changes to independent classes.
Singleton pattern
The Singleton pattern is also a useful Apex design pattern to ensure only one instance of a class gets created. This is helpful for classes that need centralized data or resource management.
Here is an example Singleton pattern implementation in Apex:
public class SettingsManager {
private static SettingsManager instance;
private Settings__c settings;
private SettingsManager() {
// Initialize the settings variable
}
public static SettingsManager getInstance() {
if(instance == null) {
instance = new SettingsManager();
}
return instance;
}
public Settings__c getSettings() {
return settings;
}
}
The key aspects are:
- The constructor is private, so no one can create additional instances
- The static getInstance() method creates and caches the instance
- All access is through the single static instance
Some usage would look like:
SettingsManager manager = SettingsManager.getInstance();
Settings__c settings = manager.getSettings();
No matter where in the code this is called from, it is always the same centralized instance of SettingsManager.
The Singleton pattern is useful when we need consistency in data (one source of truth) or centralized management of resources. Some examples include caches, configuration, loggers, service locators etc. It ensures the same instance is used application-wide.
Facade pattern
The Facade pattern is another useful Apex design pattern that provides a simple, unified interface to a complex system of components, classes and subsystems.
Here is an example Facade pattern in Apex:
public class OrderFacade {
private OrderService orderService;
private PaymentService paymentService;
private ShippingProvider shippingProvider;
public OrderFacade() {
orderService = new OrderService();
paymentService = new PaymentService();
shippingProvider = new FedExProvider();
}
public void placeOrder(Order order, CreditCard card) {
// Facade handles interactions internally
orderService.createOrder(order);
Payment payment = paymentService.createPayment(card);
shippingProvider.generateLabel(order, payment);
shippingProvider.schedulePickup(order);
}
}
And to place an order:
OrderFacade facade = new OrderFacade();
facade.placeOrder(order, card);
Rather than directly dealing with the subsystem complexity, clients use the simplified facade interface.
Some benefits are:
- Simplifies the interface for clients
- Decouples subsystems from clients
- Promotes loose coupling between components
- Helps manage complexity by hiding it
In summary, the Facade provides a simple gateway to access complex systems behind-the-scenes transparently. This is useful when we want to connect many moving parts together and simplify usage for other classes.
In Summary
Design patterns might seem complex at first but boil down to common sense object-oriented programming principles we all intrinsically want to follow. The biggest challenge lies not in understanding patterns but remembering to actively apply them in our codebases!
I hope walking through Apex-specific implementations of key patterns helps shorten this gap. Next time you are building a new feature, pause for a second and think whether one of these templates could save you effort while making your architecture more robust. Over time, patterns will become second nature to reach for.
The creative freedom Apex provides can also become its biggest downfall without the guardrails enforced in strongly typed ecosystems like Java. Following community-vetted design patterns is what allows balancing rapid development with scale and governance in Apex. They encapsulate wisdom that would otherwise take teams years of refactoring to learn. Stand on their shoulders to build faster and better!
Explore Apex related post / content here
For more join, our telegram channel, Subscribe SFDCLesson YouTube channel.
About the blog
SFDCLessons is a blog where you can find various Salesforce tutorials and tips that we have written to help beginners and experienced developers alike. we also share my experience and knowledge on Salesforce best practices, troubleshooting, and optimization. Don’t forget to follow us on:
Newsletter
Subscribe to our email newsletter to be notified when a new post is published.

Thanks for sharing this in-depth article