Design Patterns: Singleton in Salesforce Apex
The singleton pattern behaves a little differently in Salesforce than in languages like C# or Java. In Apex, a singleton instance persists only for the lifetime of the current transaction (execution context). This leads some developers to dismiss the pattern as “useless” in Salesforce. However, this view is incomplete: singletons can be extremely useful within a single execution, especially when repeated object creation or data retrieval would otherwise waste CPU or heap resources.
A singleton is simply a class with a private constructor and a static accessor method. The accessor instantiates the class once and returns the same instance for subsequent calls. This minimizes redundant work, keeps configuration centralized, and ensures consistency.
Example:
public class ConfigService {
private static ConfigService instance;
private Map<String, My_Config__mdt> settings;
// Private constructor
private ConfigService() {
settings = new Map<String, My_Config__mdt>();
for (My_Config__mdt cfg : [
SELECT DeveloperName, Setting_Value__c FROM My_Config__mdt
]) {
settings.put(cfg.DeveloperName, cfg);
}
}
// Public accessor
public static ConfigService getInstance() {
if (instance == null) {
instance = new ConfigService();
}
return instance;
}
public String getValue(String key) {
return settings.containsKey(key) ? settings.get(key).Setting_Value__c : null;
}
}
ConfigService usage:
String threshold = ConfigService.getInstance().getValue('DiscountThreshold');
Use singleton for lean and efficient code:
- Avoids duplication: Ensures an object or configuration is created once and reused.
- Minimizes overhead: Cuts down on redundant SOQL queries, DML operations, or object instantiations that waste CPU cycles.
- Streamlines memory usage: Stores frequently accessed data in a single instance, balancing CPU savings with minimal heap cost.
- Keeps logic centralized: Encapsulates common retrieval or processing logic in one place, making the codebase easier to maintain.
- Improves consistency: Guarantees uniform values across the transaction, reducing the risk of mismatched or outdated references.
- Supports scalability: Helps long-running or complex transactions stay efficient by reducing unnecessary work.
- Promotes reusability: A well-designed singleton can serve multiple classes and contexts without rewriting logic.