Classic software design patterns translate beautifully to Salesforce Apex, but they look a bit different. The Singleton pattern ensures one shared instance per transaction (great for caching). The Strategy pattern lets you swap out business rules cleanly. The Factory pattern creates the right class dynamically based on configuration. Mastering these patterns—and knowing which ones the Salesforce platform handles natively—is the key to writing scalable, enterprise-grade code.
Imagine your company handles global orders: US orders require a Tax ID, while EU orders require a VAT check. Instead of a massive
if/else chain, use the Strategy pattern. Create one
IOrderValidator interface and separate classes for each country. Then, use a Factory to read a Custom Metadata Type and load the correct class dynamically. When you expand to a new country, you just write a new class and add one config row. The core logic remains untouched!
Core Concept: Translating GoF Patterns to Apex
In technical interviews and architectural reviews, you'll often be asked how you apply the classic Gang of Four (GoF) design patterns to Salesforce. The secret isn't just writing the patterns from scratch; it's understanding how Salesforce features already implement them.
- Singleton: One instance per transaction. Perfect for trigger-bypass flags or caching org settings.
- Strategy: Swapping algorithms behind an interface so a specific pricing or discount rule can be chosen at runtime.
- Factory: Creating the correct object instance dynamically (often driven by Custom Metadata).
- Facade: Providing a simple entry point over a complex subsystem. A well-designed Service Layer method is a facade.
- Observer: A publish-and-subscribe model. In Salesforce, Platform Events act as the native Observer pattern.
- Decorator: Wrapping an object to add behavior. In Apex, this is typically your UI "Wrapper Class."
- Template Method: A base class that defines a skeleton, letting subclasses fill in the specific steps.
Database.Batchableis a prime example.
The Apex Pattern Mapping Guide
GoF Pattern → Salesforce Incarnation --------------------------------------------------------- Singleton → Static instance / Trigger bypass flag Strategy → Interface + Swappable classes (e.g., Discount Engine) Factory → Type.forName() driven by Custom Metadata Facade → Service-layer method wrapping Selectors + Domain logic Observer → Platform Events / Change Data Capture (CDC) Decorator → Wrapper classes for UI/LWC data tables Template → Abstract base classes (e.g., Batchable skeleton) Command → Queueable interface (a self-contained action)
๐งญ 360 Card: Design Patterns in Apex
- The Rule: Identify the pattern you need, then determine if Salesforce natively provides it before writing custom code.
- The Gain: Combining a Strategy with a Factory means "new business rules" translate to adding a simple config row rather than rewriting core classes.
- The Price: Applying a pattern without a real business need creates unnecessary indirection (spaghetti architecture). Wait until a change request happens twice before abstracting it.
- The Limits: Apex Singletons only live for a single transaction context. They are not JVM-style singletons that persist forever. For cross-transaction caching, use Platform Cache.
- The Mirror: A giant 300-line
if/elseblock is easy to read on day one, but becomes a merge-conflict nightmare by day 100.
Core Q&A
The Implementation:
- Define an interface:
IDiscountStrategy { Decimal calculate(Order__c o); } - Write an implementing class for each rule family (e.g.,
TierDiscount,PromoDiscount). - Create a Factory class that reads Custom Metadata to find the right class name, then instantiates it using
Type.forName(className).newInstance().
This adheres strictly to the Open/Closed Principle. Your code is open for extension (adding new classes) but closed for modification (you never touch the core engine).
// 1. The Interface
public interface IDiscountStrategy {
Decimal calculate(Order__c o);
}
// 2. A Specific Strategy
public class TierDiscount implements IDiscountStrategy {
public Decimal calculate(Order__c o) {
// Complex logic here
return 0.10;
}
}
// 3. The Dynamic Factory
public class DiscountFactory {
public static IDiscountStrategy get(String className) {
// Builds the class dynamically via String name from Custom Metadata
return (IDiscountStrategy) Type.forName(className).newInstance();
}
}
The most common use case is a Trigger Bypass Manager or a recursion control flag.
You create a class with a private static instance and expose it via a getInstance() method. It holds state (like a Set<Id> of records already processed). The critical interview detail here is lifetime. Unlike Java or C#, an Apex static variable (and therefore an Apex Singleton) only lives for the duration of a single transaction. It resets entirely when the transaction ends.
Do not build custom database tables to act as event queues (Observer pattern). This burns DML limits, requires heavy polling, and causes record-locking issues. The platform natively provides Platform Events and Change Data Capture (CDC) to handle highly scalable publish/subscribe architectures.
Real-Life Troubleshooting Check
Scenario 1: The Monolith
Problem: Your Account trigger handler is 3,000 lines long, and developers are terrified to deploy on Fridays because everything breaks.
Solution: Implement the Facade pattern via a Service Layer. Break the logic down using the Strangler Fig pattern—start by moving SOQL queries to a Selector layer, then migrate business logic into domain classes.
Scenario 2: The Slow Test Suite
Problem: Your unit tests take 90 minutes to run in the CI/CD pipeline.
Solution: You lack Dependency Injection (Factory/Strategy patterns). Because the code is tightly coupled, every test is forcing real database DML and trigger executions. Refactor to use interfaces so you can mock the data and test pure logic in milliseconds.