Strategy Pattern allows you to make a game-time call. The strategy pattern is a behavioral pattern that helps you control dynamic application flow. Strategy Design Pattern in Apex allows us to define a family of algorithms through multiple classes which implement an interface. The choice of which implementation to use is determined at runtime.
What is the Strategy Design Pattern in Apex?
The strategy pattern ensures that different solutions are available for the same problem. It chooses the appropriate solution based on the user input at the runtime. This design pattern is also called the policy pattern. When do we want to use the strategy pattern? We want to use the strategy pattern whenever we have a deeply nested switch or conditional if-else statement within our application’s core logic.
Here’s an example of a switch statement and it’s pretty easy to read because it’s so simple in its nature. What we’re doing is we’re taking the record types developer name. When it’s retail we’ll handle the retail request. When it’s wholesale we’ll handle the wholesale request.
@AuraEnabled(cacheable=true)
public static FactResponse getAnimalFacts(Id recordId) {
FactResponse result;
String recordTypeDeveloperName = selector.getAccountFromId(recordId).RecordType.DeveloperName;
switch on recordTypeDeveloperName {
when 'Retail' {
result = handleRetailRequest();
}
when 'Wholesale' {
result = handleWholesaleRequest();
}
// ...
}
return result;
}
But the problem is that as your application grows and matures this different context that you’re going to need to be executing in might become more and more complex. You’re going to need to keep writing these multiple separate handlers for every different context. You find yourself and each one of these handlers is just like another private method within this one particular class so the responsibility of the class actually grows and grows and grows as you handle all of these different contexts.
So in order to help our code base stay easier to maintain and easier to read in the long run we’re going to try to introduce the strategy design pattern here.
Class Diagram for the Strategy pattern
Here is a class diagram for the Strategy pattern. Few key concepts, we’ll always start with some context and that context depends upon the interface. We call this interface the strategy interface. Here we’re just saying this interface has a void method called an algorithm.
Then we’ll have multiple strategies concrete implementations or concrete strategies that implement the strategy interface. The context will choose the strategy that it needs at runtime and call it’s about the implementation of the algorithm.
Implementation of Strategy pattern in Apex
Let’s see how we can implement the Strategy pattern in Apex.
public class AnimalService {
private static final Map<String, IAnimalService> RECORD_TYPE_TO_SERVICE = new Map<String, IAnimalService>{
'Retail' => new DogService(),
'Wholesale' => new CatService()
};
@TestVisible
private static AnimalService.Selector selector = new AnimalService.Selector();
@AuraEnabled(cacheable=true)
public static FactResponse getAnimalFacts(Id recordId) {
String context = selector.getAccountFromId(recordId)
.RecordType.DeveloperName;
IAnimalService strategy = RECORD_TYPE_TO_SERVICE.get(context);
return strategy.getFacts();
}
}
Here is an example of an Interface class.
public interface IAnimalService {
FactResponse getFacts();
}
public class FactResponse {
@AuraEnabled public String factAbout;
@AuraEnabled public String fact;
}
@TestVisible
private with sharing class Selector {
public virtual Account getAccountFromId(Id recordId) {
return [ SELECT Id, RecordType.DeveloperName FROM Account WHERE Id = :recordId];
}
}
}
So keep in mind here that what we care about is this interface right we’re relying on the interface to tell us how we’re going to get a fact response and we don’t really care if we’re going to get dog responses or if we’re going to get cat responses. All that we care about is hey you’re just going to tell us to get facts and we will get you facts and a factor response back.
Cat Service
public class CatService implements AnimalService.IAnimalService {
public AnimalService.FactResponse getFacts() {
AnimalService.FactResponse result = new AnimalService.FactResponse();
Http http = new Http();
HttpRequest request = new HttpRequest();
request.setEndpoint('https://catfact.ninja/fact');
request.setMethod('GET');
HttpResponse response = http.send(request);
if (response.getStatusCode() == 200) {
result.factAbout = 'cats';
CatService.StructuredResponse catFact = (CatService.StructuredResponse) (JSON.deserialize(
response.getBody(),
CatService.StructuredResponse.class
));
result.fact = catFact.fact;
}
return result;
}
}
Response class
public class StructuredResponse {
@AuraEnabled
public String fact;
@AuraEnabled
public Integer length;
}
Dog Service
public class DogService implements AnimalService.IAnimalService {
public AnimalService.FactResponse getFacts() {
AnimalService.FactResponse result = new AnimalService.FactResponse();
Http http = new Http();
HttpRequest request = new HttpRequest();
request.setEndpoint('https://dog-facts-api.herokuapp.com/api/v1/resources/dogs?number=1');
request.setMethod('GET');
HttpResponse response = http.send(request);
if (response.getStatusCode() == 200) {
result.factAbout = 'dogs';
List<DogService.StructuredResponse> facts = (List<DogService.StructuredResponse>) (JSON.deserialize(
response.getBody(),
List<DogService.StructuredResponse>.class
));
result.fact = facts[Integer.valueOf((Math.random() * facts.size()))].fact;
}
return result;
}
}
Dog response class
public class StructuredResponse {
@AuraEnabled
public String fact;
}
Dynamic Mapping
Now here is an example of how we can convert our first class into a Strategy Design Pattern.
public class AnimalService {
private static final Map<String, IAnimalService> RECORD_TYPE_TO_SERVICE = new Map<String, IAnimalService>{
'Retail' => new DogService(),
'Wholesale' => new CatService()
};
...
}
Use Custom Metadata
Use custom metadata to make the above class dynamic with custom metadata.
private Map<String, IAnimalService> getContextMapFromMetadata() {
Map<String, IAnimalService> result;
for (
RecordType_To_Service__mdt recordTypeToServiceMapping : RecordType_To_Service__mdt.getAll()
.values()
) {
String key = recordTypeToServiceMapping.RecordType_DeveloperName__c;
IAnimalService value = (IAnimalService) (Type.forName(
recordTypeToServiceMapping.Apex_Class_Name__c
)
.newInstance());
result.put(key, value);
}
return result;
}
Strategy Design Pattern in Apex Video
Check out our recording for a line-by-line explanation of the code.
Check our other apex design pattern in our blog post.
When should we use Strategy Design Pattern?
Strategy pattern should be used when you need to choose between one of many algorithms to solve the problem, and you won’t know which one to use until runtime.
Strategy pattern helps to break out custom implementations from the main code path while minimizing the use of large switch statements.
Summary
Strategy Design Pattern is used to define a family of algorithms, encapsulating each one and making them interchangeable and selectable at runtime. Check recording for real-time examples using LWC in Apex.
Thank you for explaining this to us.
Design patterns are hard to grasp but you’re making it easier to us.
I think I found a typo in the Interface Class:
public interface IAnimalService {
FactResponse getFacts();
} -> this curly bracket is marking the end of the Interface but if you scroll down through the class there is another curly bracket that is supposed to mark the end of the class.
Can you please check?
Let me check and fix it
Hi Amit,
Appreciate your contribution to the community but this isn’t a good implementation of the pattern.
This code has massive resource/performance problems:
private static final Map RECORD_TYPE_TO_SERVICE = new Map{
‘Retail’ => new DogService(),
‘Wholesale’ => new CatService()
};
You are instantiating all the depended classes (Dog and CatService) at the moment of initialization of the main class which causes their constructors to run and puts their instances in RAM which consumes both CPU time and heap. Now imagine there’s 10 strategy classes each with complex logic in the constructor. This is a time bomb and will cause performance issues in the best case or LimitException in the worst case scenario down the line.
But you don’t even need all the instances strategy classes at this point. You *will* need just one instance of one particular class during runtime. So the way this is usually done is by keeping a map of the name to the type of the strategy class rather than the instance:
private static final Map RECORD_TYPE_TO_SERVICE = new Map{
‘Retail’ => DogService.class.
‘Wholesale’ => CatService.class
};
And to use it:
IAnimalService strategy = (IAnimalService) RECORD_TYPE_TO_SERVICE.get(context).newInstance();
Hi Amit,
I need brief understanding about different design patterns available in salesforce and their uses. Can you please help me on this.
Here we go https://www.apexhours.com/apex-design-patterns/. we documented few