Strategy Design Pattern in Apex

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.

Class Diagram for the Strategy pattern

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.

YouTube video

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.

Amit Chaudhary
Amit Chaudhary

Amit Chaudhary is Salesforce Application & System Architect and working on Salesforce Platform since 2010. He is Salesforce MVP since 2017 and have 17 Salesforce Certificates.

He is a active blogger and founder of Apex Hours.

Articles: 461

5 Comments

  1. 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?

  2. 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();

  3. Hi Amit,

    I need brief understanding about different design patterns available in salesforce and their uses. Can you please help me on this.

Leave a Reply

Your email address will not be published. Required fields are marked *