Design patterns are proven solutions to recurring software design problems. In Salesforce development, implementing appropriate design patterns helps create maintainable, scalable, and robust applications. This guide covers the most important design patterns used in Salesforce development, their implementations, benefits, and specific use cases.
The Model-View-Controller (MVC) pattern separates an application into three interconnected components, providing a clear separation of concerns.

// Model
public class AccountModel {
private Account acc;
public AccountModel(Account acc) {
this.acc = acc;
}
public String getAccountName() {
return acc.Name;
}
public void updateAccountName(String name) {
acc.Name = name;
}
}
// Controller
public class AccountController {
private AccountModel model;
public AccountController() {
this.model = new AccountModel([SELECT Id, Name FROM Account LIMIT 1]);
}
public String getAccountName() {
return model.getAccountName();
}
public void saveAccount(String name) {
model.updateAccountName(name);
}
}
<!-- View (Visualforce) -->
<apex:page controller="AccountController">
<apex:form>
<apex:outputLabel value="Account Name: {!accountName}" />
<apex:inputText value="{!accountName}" />
<apex:commandButton value="Save" action="{!saveAccount}" />
</apex:form>
</apex:page>
The Factory pattern provides an interface for creating objects without specifying their concrete classes, delegating instantiation to subclasses.

// Product Interface
public interface NotificationService {
void sendNotification(String message, String recipient);
}
// Concrete Products
public class EmailNotification implements NotificationService {
public void sendNotification(String message, String recipient) {
// Email implementation
}
}
public class SMSNotification implements NotificationService {
public void sendNotification(String message, String recipient) {
// SMS implementation
}
}
// Factory Class
public class NotificationFactory {
public static NotificationService createNotificationService(String type) {
if (type == 'Email') {
return new EmailNotification();
} else if (type == 'SMS') {
return new SMSNotification();
} else {
throw new UnsupportedOperationException('Notification type not supported');
}
}
}
// Client Usage
NotificationService service = NotificationFactory.createNotificationService('Email');
service.sendNotification('Hello', '[email protected]');
The Singleton pattern ensures a class has only one instance and provides a global point of access to it.

public class Logger {
// Private static instance variable
private static Logger instance;
// Private constructor to prevent direct instantiation
private Logger() {
// Initialization code
}
// Public static method to get the instance
public static Logger getInstance() {
if (instance == null) {
instance = new Logger();
}
return instance;
}
// Logger methods
public void log(String message) {
System.debug(message);
}
}
// Usage
Logger logger = Logger.getInstance();
logger.log('This is a log message');
The Observer pattern defines a one-to-many dependency between objects, so when one object changes state, all its dependents are notified and updated automatically.

// Observer Interface
public interface Observer {
void update(String message);
}
// Concrete Observer
public class CustomerObserver implements Observer {
public void update(String message) {
System.debug('Customer notified: ' + message);
}
}
// Subject
public class OrderSubject {
private List<Observer> observers = new List<Observer>();
public void attach(Observer observer) {
observers.add(observer);
}
public void detach(Observer observer) {
observers.remove(observers.indexOf(observer));
}
public void notifyObservers(String message) {
for (Observer observer : observers) {
observer.update(message);
}
}
public void processOrder(String orderNumber) {
// Process order logic
notifyObservers('Order ' + orderNumber + ' has been processed');
}
}
// Usage
OrderSubject orderSubject = new OrderSubject();
Observer customerObserver = new CustomerObserver();
orderSubject.attach(customerObserver);
orderSubject.processOrder('12345');
The Strategy pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable, allowing the algorithm to vary independently from clients using it.

// Strategy Interface
public interface PricingStrategy {
Decimal calculatePrice(Decimal basePrice);
}
// Concrete Strategies
public class RegularPricingStrategy implements PricingStrategy {
public Decimal calculatePrice(Decimal basePrice) {
return basePrice;
}
}
public class DiscountPricingStrategy implements PricingStrategy {
private Decimal discountPercent;
public DiscountPricingStrategy(Decimal discountPercent) {
this.discountPercent = discountPercent;
}
public Decimal calculatePrice(Decimal basePrice) {
return basePrice * (1 - discountPercent / 100);
}
}
// Context
public class PriceCalculator {
private PricingStrategy strategy;
public void setStrategy(PricingStrategy strategy) {
this.strategy = strategy;
}
public Decimal calculateFinalPrice(Decimal basePrice) {
return strategy.calculatePrice(basePrice);
}
}
// Usage
PriceCalculator calculator = new PriceCalculator();
calculator.setStrategy(new RegularPricingStrategy());
Decimal regularPrice = calculator.calculateFinalPrice(100);
calculator.setStrategy(new DiscountPricingStrategy(10)); // 10% discount
Decimal discountedPrice = calculator.calculateFinalPrice(100);
The Composite pattern lets you compose objects into tree structures to represent part-whole hierarchies, allowing clients to treat individual objects and compositions uniformly.
// Component
public interface OrgStructure {
void display(Integer depth);
Decimal calculateBudget();
}
// Leaf
public class Department implements OrgStructure {
private String name;
private Decimal budget;
public Department(String name, Decimal budget) {
this.name = name;
this.budget = budget;
}
public void display(Integer depth) {
String indent = '';
for (Integer i = 0; i < depth; i++) {
indent += '--';
}
System.debug(indent + name);
}
public Decimal calculateBudget() {
return budget;
}
}
// Composite
public class Organization implements OrgStructure {
private String name;
private List<OrgStructure> children = new List<OrgStructure>();
public Organization(String name) {
this.name = name;
}
public void add(OrgStructure component) {
children.add(component);
}
public void remove(OrgStructure component) {
children.remove(children.indexOf(component));
}
public void display(Integer depth) {
String indent = '';
for (Integer i = 0; i < depth; i++) {
indent += '--';
}
System.debug(indent + name);
for (OrgStructure child : children) {
child.display(depth + 1);
}
}
public Decimal calculateBudget() {
Decimal totalBudget = 0;
for (OrgStructure child : children) {
totalBudget += child.calculateBudget();
}
return totalBudget;
}
}
// Usage
Organization company = new Organization('Acme Corp');
Organization sales = new Organization('Sales Division');
Organization marketing = new Organization('Marketing Division');
sales.add(new Department('Enterprise Sales', 1000000));
sales.add(new Department('SMB Sales', 500000));
marketing.add(new Department('Digital Marketing', 250000));
company.add(sales);
company.add(marketing);
company.add(new Department('R&D', 2000000));
company.display(0);
System.debug('Total Budget: ' + company.calculateBudget());
The Service Layer pattern defines an application’s boundary and its set of available operations from the perspective of interfacing client layers.
// Service Interface
public interface AccountService {
List<Account> getAccounts();
Account getAccountById(Id accountId);
void saveAccount(Account account);
}
// Service Implementation
public class AccountServiceImpl implements AccountService {
public List<Account> getAccounts() {
return [SELECT Id, Name, Industry FROM Account LIMIT 100];
}
public Account getAccountById(Id accountId) {
return [SELECT Id, Name, Industry FROM Account WHERE Id = :accountId];
}
public void saveAccount(Account account) {
upsert account;
}
}
// Usage in Controller
public class AccountController {
private AccountService service;
public AccountController() {
this.service = new AccountServiceImpl();
}
public List<Account> getAccounts() {
return service.getAccounts();
}
}
The Repository pattern mediates between the domain and data mapping layers, acting like an in-memory collection of domain objects.
// Repository Interface
public interface ContactRepository {
List<Contact> getByAccountId(Id accountId);
Contact getById(Id contactId);
void save(Contact contact);
void delete(Id contactId);
}
// Repository Implementation
public class ContactRepositoryImpl implements ContactRepository {
public List<Contact> getByAccountId(Id accountId) {
return [SELECT Id, FirstName, LastName, Email FROM Contact
WHERE AccountId = :accountId];
}
public Contact getById(Id contactId) {
return [SELECT Id, FirstName, LastName, Email, AccountId
FROM Contact WHERE Id = :contactId];
}
public void save(Contact contact) {
upsert contact;
}
public void delete(Id contactId) {
delete [SELECT Id FROM Contact WHERE Id = :contactId];
}
}
// Usage in Service
public class ContactService {
private ContactRepository repository;
public ContactService() {
this.repository = new ContactRepositoryImpl();
}
public List<Contact> getContactsByAccount(Id accountId) {
return repository.getByAccountId(accountId);
}
}
The Selector pattern centralizes SOQL queries to provide a single point of access for retrieving records.
public class AccountSelector {
public static List<Account> getAccountsByIds(Set<Id> accountIds) {
return [SELECT Id, Name, Industry, BillingCity, BillingCountry
FROM Account
WHERE Id IN :accountIds];
}
public static List<Account> getAccountsWithContacts() {
return [SELECT Id, Name,
(SELECT Id, FirstName, LastName FROM Contacts)
FROM Account
WHERE Id IN (SELECT AccountId FROM Contact)];
}
public static List<Account> getAccountsByIndustry(String industry) {
return [SELECT Id, Name, Industry
FROM Account
WHERE Industry = :industry];
}
}
// Usage
List<Account> techAccounts = AccountSelector.getAccountsByIndustry('Technology');
The Domain Layer pattern encapsulates business logic related to a specific domain entity.
public class AccountDomain {
private List<Account> accounts;
public AccountDomain(List<Account> accounts) {
this.accounts = accounts;
}
public void applyDiscount(Decimal discountPercent) {
for (Account acc : accounts) {
// Apply discount logic to accounts
applyDiscountToOpportunities(acc.Id, discountPercent);
}
}
private void applyDiscountToOpportunities(Id accountId, Decimal discountPercent) {
List<Opportunity> opps = [SELECT Id, Amount FROM Opportunity WHERE AccountId = :accountId];
for (Opportunity opp : opps) {
opp.Amount = opp.Amount * (1 - discountPercent / 100);
}
update opps;
}
public void assignTerritories() {
// Territory assignment logic
}
}
// Usage
List<Account> accounts = [SELECT Id FROM Account WHERE Industry = 'Retail'];
AccountDomain domain = new AccountDomain(accounts);
domain.applyDiscount(10);
Salesforce applications can leverage a combination of patterns to create a comprehensive architecture. A common approach is:
// Trigger
trigger AccountTrigger on Account (before insert, before update) {
AccountTriggerHandler handler = new AccountTriggerHandler();
if (Trigger.isBefore) {
if (Trigger.isInsert) {
handler.onBeforeInsert(Trigger.new);
}
else if (Trigger.isUpdate) {
handler.onBeforeUpdate(Trigger.new, Trigger.oldMap);
}
}
}
// Trigger Handler
public class AccountTriggerHandler {
private AccountService service;
public AccountTriggerHandler() {
this.service = new AccountServiceImpl();
}
public void onBeforeInsert(List<Account> newAccounts) {
service.validateAccounts(newAccounts);
service.enrichAccounts(newAccounts);
}
public void onBeforeUpdate(List<Account> newAccounts, Map<Id, Account> oldAccountMap) {
service.validateUpdates(newAccounts, oldAccountMap);
}
}
// Service Layer
public class AccountServiceImpl implements AccountService {
private AccountDomain domain;
private AccountSelector selector;
public AccountServiceImpl() {
this.selector = new AccountSelector();
}
public void validateAccounts(List<Account> accounts) {
domain = new AccountDomain(accounts);
domain.validate();
}
public void enrichAccounts(List<Account> accounts) {
domain = new AccountDomain(accounts);
domain.enrichData();
}
}
// Domain Layer
public class AccountDomain {
private List<Account> accounts;
public AccountDomain(List<Account> accounts) {
this.accounts = accounts;
}
public void validate() {
// Validation logic
}
public void enrichData() {
// Data enrichment logic
}
}
// Selector Layer
public class AccountSelector {
public List<Account> getAccountsWithRelatedData(Set<Id> accountIds) {
return [SELECT Id, Name, Industry,
(SELECT Id FROM Contacts),
(SELECT Id FROM Opportunities)
FROM Account
WHERE Id IN :accountIds];
}
}