Salesforce Design Patterns - Strategy Factory
Written by Zackary Frazier, posted on 2025-01-12
- SALESFORCE
- TUTORIAL
Strategy Pattern
The strategy pattern is a software design pattern that enables algorithms to be selected at runtime, typically by using interfaces, virtual classes, and abstract classes. It's useful for two reasons:
- It simplifies the code.
- It makes the code more testable.
Essentially, in Apex, the strategy pattern boils down to this,
- Create an interface with a method to be implemented.
- Create "strategy classes" that implement this interface.
In JavaScript, this will have to be designed differently, since you don't need (and JavaScript doesn't have) interfaces. In JavaScript this essentially amounts to creating interchangeable functions that can take the same arguments, that each implements a different algorithm to process its arguments.
That's it. It sounds simple, but it invites another brilliant pattern to be used.
To give an example, one of the classic use-cases for the strategy pattern is payment processing.
For example, this code is awful.
public with sharing AccountService {
public void processPayment(Account acct) {
if(acct.Payment_Service__c == 'Stripe') {
StripeService.makePayment(acct);
} else if(acct.Payment_Service__c == 'Credit Card') {
CreditCardService.makePayment(acct);
} else if(acct.Payment_Service__c == 'PayPal') {
PayPalService.makePayment(acct);
} else if(acct.Payment_Service__c == 'ACH') {
AchService.makePayment(acct);
} // and so on and so on...
}
}
To clean this up we might create a strategy pattern to handle this and split each specific processing algorithm into its own handler.
public interface IPaymentStrategy {
void makePayment(Account acct);
}
public with sharing AccountService {
Map<String, Type> strategyByService = new Map<String, Type> {
'Stripe' => StripeStrategy.Type,
'Credit Card' => CreditCardStrategy.Type,
'PayPal' => PayPalStrategy.Type,
'ACH' => AchStrategy.Type
};
public void processPayment(Account acct) {
Type paymentStrategyType = strategyByService.get(
acct.Payment_Service__c
);
// gotta handle our errors...
if(paymentStrategyType == null) {
throw new IllegalArgumentException('Invalid payment method');
}
IPaymentStrategy paymentStrategy = (IPaymentStrategy)
paymentStrategyType.newInstance();
paymentStrategy.makePayment(acct);
}
}
Note the use of Apex Type
values. This reduces the memory footprint of the factory by not instantiating the strategies until they're needed via the newInstance()
method.
For folks who don't know the newInstance()
method, it's used to dynamically instantiate Apex classes on the fly. The only downside is that it doesn't take parameters. In scenarios where you need a constructor for your strategies, you can create an init
method (it doesn't have to be called that) that takes the parameters. Typically when I use this approach, my strategy is implementing a virtual class instead of an interface, so each strategy has the same init
method. How exactly I go about it depends on what specifically the code is for.
Type paymentStrategyType = strategyByService.get(paymentService);
IPaymentStrategy paymentStrategy = (IPaymentStrategy)
paymentStrategyType.newInstance();
paymentStrategy.setParams(param1, param2);
return strat;
However in JavaScript, since it doesn't have interfaces and also because functions are stand-alone entities, you'd see something more like this.
// account-service.js
import stripeStrategy from './stripe-strategy';
import creditCardStrategy from './credit-card-strategy';
import paypalStrategy from './paypal-strategy';
import achStrategy from './ach-strategy';
const strategyByService = {
'Stripe': stripeStrategy,
'Credit Card': creditCardStrategy,
'PayPal': paypalStrategy,
'ACH': achStrategy
};
export const processPayment = (acct) => {
const paymentService = acct.Payment_Service__c;
const paymentStrategy = strategyByService[paymentService];
if(!paymentStrategy) {
throw new Error('Invalid payment method');
}
paymentStrategy(acct);
}
Strategy Factory
What I like to to do to improve the testability of this pattern is combine this pattern with a factory. A factory pattern is a creational design pattern to produce objects. In the case of Apex this would be a class with a static method to produce objects based on the arguments it receives.
public with sharing class StrategyFactory {
Map<String, Type> strategyByService = new Map<String, Type> {
'Stripe' => StripeStrategy.Type,
'Credit Card' => CreditCardStrategy.Type,
'PayPal' => PayPalStrategy.Type,
'ACH' => AchStrategy.Type
};
// you'll see why we have this later...
@TestVisible
static Set<String> getKeys() {
return strategyByService.keySet();
}
public static IPaymentStrategy getStrategy(String value) {
Type strategy = strategyByValue.get(value);
IPaymentStrategy strategyImpl = (IPaymentStrategy)
strategy.newInstance();
return strategyImpl;
}
}
In JavaScript, you would implement this differently. Since JavaScript doesn't have interfaces, and because JavaScript functions can be stand-alone, you would create a map of keys to interchangeable functions that take the same arguments. Then, in your JavaScript class, create a getStrategy
function that takes the key and produces the correct strategy by indexing the map.
// strategy-service.js
import stripeStrategy from './stripe-strategy';
import creditCardStrategy from './credit-card-strategy';
import paypalStrategy from './paypal-strategy';
import achStrategy from './ach-strategy';
const strategyByService = {
'Stripe': stripeStrategy,
'Credit Card': creditCardStrategy,
'PayPal': paypalStrategy,
'ACH': achStrategy
};
// for testing
const strategyKeys = Object.keys(strategies)
const getStrategy = (key) => {
const strategyImpl = strategyByService[key];
if(!strategyImpl) {
throw new Error('Invalid strategy');
}
return strategyImpl;
}
export {
getStrategy,
strategyKeys
}
One key benefit of the strategy factory is that it is easy to automate it's testing. Because the sole responsibility of a strategy factory is to return a strategy given a key, you just need to verify that for all valid keys, a strategy is returned. The strategy factory test wouldn't test the behavior of these strategies. It's best, in my opinion, to leave those tests to the individual unit test classes for each strategy.
// in Apex
@isTest
private class StrategyFactoryTest {
@isTest
static void ensureStrategyFactoryReturnsStrategies() {
for(String key : StrategyFactory.getKeys()) {
IPaymentStrategy strat = (IPaymentStrategy)
SimpleStrategyFactory.getStrategy(key);
Assert.isNotNull(
strat,
'Expected strategy to be returned'
);
}
}
}
// in JavaScript (e.g. Jest)
import { getStrategy, strategyKeys } from 'c/lib'
describe("strategy factory", () => {
it("should return a strategy", () => {
for(const key of strategyKeys) {
const strategy = getStrategy(key)
expect(strategy).toBeDefined()
}
})
})
Now any time I add a new item to the strategy factory, the tests will ensure it's returned under the correct conditions without requiring any additional code to be written. You might think, "this is a dumb test", but remember, the sole responsibility of the strategy factory is to return a strategy given a valid value.
We've eliminated the cognitive load of figuring out how these units of code are invoked, and allowed ourselves the opportunity to just test the code.
This might sound contrived, like "why do all this?", and in simple cases I'd say "don't", but for adavanced cases - consider the alternative which is to have one giant method full of branching if and else statements.
if(val == 'abc') {
// do 'abc' logic
} else if(val == 'def') {
// do 'def' logic
} else if(val == 'ghi') {
// do 'ghi' logic
}
// and so on and so on...
// is this really better?
This method would be very annoying to test. Furthermore the logic of each strategy can only be tested by testing the entire method, which is redundant. Finally, it requires whoever's editing this code (who might not be you) to understand how the path to each unit of code is reached.
Knowing enterprise-level software, it's not unheard of for the purpose of old code to be lost to time.
Example
Per reader feedback, I've recorded this video that provides an example of the type of code one might apply a strategy pattern to, and how you might organically think to create one. This video uses real code from the Moxygen project. The decision to use a real codebase, as opposed to sample code, was deliberate. I wanted to give my readers a real example of where one might apply a strategy pattern in the wild.