Skip to main content

Mastering Salesforce Design Patterns: Gang of Four (GoF) in Apex

๐Ÿ’ฌ In plain words:

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.
๐Ÿ“Œ Example: Config-Driven Validation

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.Batchable is a prime example.
Salesforce Design Patterns GoF in Apex Architecture Mapping

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)
๐Ÿง  Key Takeaway: Don't reinvent the wheel. If you need a publish-subscribe (Observer) model, use Platform Events. If you need an isolated, deferred action (Command), use a Queueable.

๐Ÿงญ 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/else block is easy to read on day one, but becomes a merge-conflict nightmare by day 100.

Core Q&A

Q: You have a discount calculation that varies by customer tier, promotional campaign, and region. New rules are added monthly. How do you implement this in Apex?
๐ŸŽฏ Say this first: I would use the Strategy pattern combined with a Factory. We create one interface, separate classes for each rule, and use Custom Metadata to determine which class fires.

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();
    }
}
Q: Can you give a concrete use case for the Singleton pattern in Apex, and explain its specific lifetime?

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.

⚠️ Developer Trap: Custom Pub/Sub Tables

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.