A practical guide to four essential software design patterns for Salesforce developers: Singleton, Factory, Strategy and Unit of Work — with Apex examples and real-world use cases.
Introduction
Design patterns are repeatable solutions to common software design problems. For Salesforce developers, applying the right pattern can improve maintainability, reduce governor limit issues, and make code easier to test. This post explains four patterns with Apex examples and guidance for when to use each one.
1. Singleton Pattern
What it is: Ensures a class has only one instance per transaction and provides a global point of access to it.
Use case: Load configuration or Custom Metadata once per transaction to avoid redundant lookups and conserve CPU.
public class AppConfig { private static AppConfig instance; public String apiKey; public String endpoint; private AppConfig() { App_Settings__mdt setting = [SELECT API_Key__c, Endpoint__c FROM App_Settings__mdt LIMIT 1]; apiKey = setting.API_Key__c; endpoint = setting.Endpoint__c; } public static AppConfig getInstance() { if (instance == null) { instance = new AppConfig(); } return instance; } } // Usage String key = AppConfig.getInstance().apiKey; String url = AppConfig.getInstance().endpoint;
Why it matters
- Reduces redundant queries and CPU usage.
- Provides consistent configuration values across the transaction.
- Small heap cost in exchange for fewer repeated lookups.
2. Factory Pattern
What it is: Encapsulates object creation and returns different implementations of a common interface based on input or context.
Use case: Instantiate handlers (payments, notifications, approval logic) depending on record type, channel or user role.
public interface PaymentProcessor { void process(); } public class PayPalProcessor implements PaymentProcessor { public void process() { // PayPal logic } } public class StripeProcessor implements PaymentProcessor { public void process() { // Stripe logic } } public class PaymentFactory { public static PaymentProcessor getProcessor(String type) { if (type == 'PayPal') return new PayPalProcessor(); if (type == 'Stripe') return new StripeProcessor(); throw new IllegalArgumentException('Unknown payment type'); } } // Usage PaymentProcessor processor = PaymentFactory.getProcessor('Stripe'); processor.process();
Benefits
- Removes creation logic from business code.
- Makes it easier to extend support for new implementations.
- Improves testability and supports inversion of control.
3. Strategy Pattern
What it is: Encapsulates interchangeable algorithms or behaviors and allows selecting one at runtime.
Use case: Different regions, products or customers needing distinct calculations (discounts, taxes, risk scoring).
public interface DiscountStrategy { Decimal apply(Decimal price); } public class TenPercentDiscount implements DiscountStrategy { public Decimal apply(Decimal price) { return price * 0.9; } } public class Flat20Discount implements DiscountStrategy { public Decimal apply(Decimal price) { return price - 20; } } public class DiscountService { private DiscountStrategy strategy; public DiscountService(DiscountStrategy strategy) { this.strategy = strategy; } public Decimal getDiscountedPrice(Decimal price) { return strategy.apply(price); } } // Usage DiscountService service = new DiscountService(new Flat20Discount()); Decimal discounted2 = service.getDiscountedPrice(100); // Returns 80
Why use it
- Avoids complex conditional logic.
- Makes business rules easier to update and test individually.
4. Unit of Work Pattern
What it is: Tracks changes to multiple records and commits them together to minimize DML statements and preserve consistency.
Use case: Complex trigger logic or service layers that must perform grouped inserts/updates efficiently.
public class UnitOfWork { private ListnewAccounts = new List (); private List modifiedContacts = new List (); public void registerNew(Account acc) { newAccounts.add(acc); } public void registerModified(Contact con) { modifiedContacts.add(con); } public void commit() { if (!newAccounts.isEmpty()) insert newAccounts; if (!modifiedContacts.isEmpty()) update modifiedContacts; } } // Example usage public class AccountHandler { public static void process(List accounts) { UnitOfWork uow = new UnitOfWork(); for (Account acc : accounts) { acc.Name += ' - Processed'; uow.registerNew(acc); Contact c = new Contact(LastName = acc.Name, AccountId = acc.Id); uow.registerModified(c); } uow.commit(); } }
Advantages
- Reduces scattered DML operations.
- Helps prevent hitting governor limits.
- Centralizes commit logic for better maintainability.
Key Takeaways
- Use Singleton for shared configuration and to avoid repeated lookups.
- Use Factory to centralize object creation and support multiple implementations.
- Use Strategy to encapsulate business algorithms and swap them at runtime.
- Use Unit of Work to batch DML and improve transaction efficiency.
Final Thoughts
Design patterns are practical shortcuts that promote scalable, testable Apex code. These four patterns are widely applicable across Salesforce orgs and help teams reduce duplication, improve maintainability, and manage governor limits.
Why this matters for admins, developers and business users: Applying the right patterns leads to more reliable automation, clearer ownership of business logic, and systems that are easier to extend—reducing friction for admins and delivering faster, safer changes for the business.
Leave a Reply