Design Patterns: Factory in Salesforce Apex
The Factory pattern is one of the most practical and commonly used creational design patterns. Unlike the Singleton pattern, which focuses on ensuring only one instance exists, the Factory pattern focuses on object creation without exposing the instantiation logic to the client.
In Salesforce Apex, the Factory pattern is particularly useful when your application must deal with multiple implementations of the same abstraction, for example, integrating with different payment providers such as Stripe, PayPal, or Revolut.
Example: Payment Processing with a Factory
Let’s say you want to process payments through different providers. Without a Factory, your code might be littered with conditional logic:
if (provider == 'Stripe') {
// Stripe logic
} else if (provider == 'PayPal') {
// PayPal logic
}
Step 1: Define an interface for payment processors
public interface IPaymentProcessor {
void processPayment(Decimal amount);
}
Step 2: Create concrete implementations
public class StripeProcessor implements IPaymentProcessor {
public void processPayment(Decimal amount) {
System.debug('Processing ' + amount + ' via Stripe');
// Callout logic for Stripe
}
}
public class PayPalProcessor implements IPaymentProcessor {
public void processPayment(Decimal amount) {
System.debug('Processing ' + amount + ' via PayPal');
// Callout logic for PayPal
}
}
Step 3: Build the Factory class
public class PaymentProcessorFactory {
public static IPaymentProcessor getProcessor(String provider) {
switch on provider {
when 'Stripe' {
return new StripeProcessor();
}
when 'PayPal' {
return new PayPalProcessor();
}
when else {
throw new IllegalArgumentException('Unsupported payment provider: ' + provider);
}
}
}
}
Step 4: Use the Factory
IPaymentProcessor processor = PaymentProcessorFactory.getProcessor('Stripe');
processor.processPayment(100.00);
Why use the Factory in Salesforce?
- Encapsulates complexity: The creation of processors is handled in one place.
- Easier to extend: Adding a new provider requires only a new implementation and one when clause.
- Promotes consistency: Prevents inconsistent instantiation logic scattered across the org.
- Supports testing: Swap real processors for mock ones in unit tests without changing business logic.
- Encourages loose coupling: Clients rely on the IPaymentProcessor interface, not on specific classes.