Apex Design Patterns

Repeated execution of a class within a single transaction may lead to exceed the governor limit. This pattern is to reduce repeated instantiation of a class.

Singleton:

Repeated execution of a class within a single transaction may lead to exceed the governor limit. This pattern is to reduce repeated instantiation of a class.

Strategy:

Strategy pattern is to provide different solutions for the same problem at runtime.

Decorator:

Generally, this pattern is used to provide the additional functionality to the sObject using Apex code.

Bulk State Transition: Implementing Bulk State transition ensure that the bulk action is performed based on the change of bulk record state.

Singleton pattern 

The use case of the singleton pattern is to create only one instance of a class per transaction. Consider the following scenario, I have two custom objects, “products” and “customer”. The “customer” object has “Customer Name” (Text field), “Type” (Picklist field with values Car, Bus and Bike) and a lookup field named “Product Name” to the Product Object. The “products” object has “Product Name” (Text field), “Product No” (Text field), “Amount” (Currency field).

Whenever the customer provides the Type as “Car”, then the record should be associated with the “car” products. The following is the implementation code.

Trigger 

trigger CustomerTrigger_AT on Customer__c (before insert) { 

    for(Customer__c cust: Trigger.new){ 

         if(cust.Type__c == 'Car'){ 

        //Instantiate the Carproduct_AC class to get Car product Id. 

         CarProduct_AC carId = new CarProduct_AC(); 

         cust.ProductName__c = carId.recordId;   

         }   

    } 

}

Class 

public class CarProduct_AC{ 

    public String recordId {get; private set;} 

    public CarProduct_AC(){ 

    //To get the record Id for a product named “Car”.  

    //If a trigger is executed in bulk, it exceeds the SOQL limitation. 

           recordId = [SELECT Id FROM Products__c WHERE Name = 'CAR'].Id; 

    } 

}

In the above trigger, for each record it calls the Apex class “CarProduct_AC”. It exceeds the Apex SOQL limitation for the bulk transaction and throws “CustomerTrigger: System.LimitException: Too many SOQL queries: 101” error. To overcome this problem, we are going with the singleton design pattern. The method in this class need not to run for each record. It will provide the car’s record Id when it runs for the first time. So, here we have stopped the class from recursive execution using Singleton pattern.

Trigger 

trigger CustomerTrigger_AT on Customer__c (before insert, before update) { 

    for(Customer__c cust : Trigger.new){ 

         if(cust.Type__c == 'Car'){ 

         //Instantiate the Carproduct_AC class to get Car product Id. 

         CarProduct_AC carId = CarProduct.getinstance(); 

         cust.ProductName__c = carId.recordId;   

         }   

    } 

}

Singleton Class 

public class CarProduct_AC{ 

    //Static variable to refer to the class instance 

    private static CarProduct_AC instance = null; 

    //Private constructor to get the record Id. 

    public String recordId {get;private set;} 

    private CarProduct_AC(){ 

        //To get the record Id for car product.  

        recordId = [SELECT Id FROM Products__c WHERE Name = 'CAR'].Id; 

    } 

    public static CarProduct_AC getInstance(){ 

        if(instance == null) instance = new CarProduct_AC(); 

        return instance; 

    } 

}

For a single transaction, the query gets executed only once. This is because we are not instantiating the CarProduct_AC for each customer’s record insert, it gets instantiated only once per transaction.
Strategy Pattern 

The strategy pattern is ensuring that different set of solutions is available for a same problem. It chooses the appropriate solution based on the user input at the runtime. Let’s consider the Opportunity object with Type picklist has values Discount 1 and Discount 2. Our goal is to calculate and apply a unique discount percentage for the opportunity amount based on the selected Type.

Discount Logic: 

Discount 1 – Opportunity created date is from 1 to 15 of any month, discount = 5%.

Discount 1 – Opportunity created date is from 15 to end of any month, discount = 10%

Discount 2 – Opportunity created date is from 1 to 15 of any month, discount = 15%.

Discount 2 – Opportunity created date is from 15 to end of any month, discount = 20%

The following code shows how the strategy pattern provides the different set of solutions with code reusability at runtime. I used custom setting to map opportunity type and apex class (different solution based on type) to provide the solution.

Context Class 

This class decides the solution at runtime based on user requirement.

public class OpportunityTypeContext_AC { 

    Map<Id,Opportunity> opportunityMap = new Map<Id,Opportunity>();     

    public void onBeforeInsert(List<Opportunity> opportunityList){ 

        for(Opportunity opp : opportunityList){ 

          //Get the opportunity with Discount 1 and Discount 2 values 

            if(opp.Amount != null && opp.Type != null && (opp.Type == 'Discount 1' || 

                                                                                                 opp.Type == 'Discount 2')){ 

                opportunityMap.put(opp.Id,opp); 

            } 

        } 

        if(opportunityMap.size() > 0) { 

            applyDiscount(opportunityMap);  

        } 

    }   

    //Apply discount based on opportunity type 

    public void applyDiscount(Map<Id,Opportunity> opportunityMap){ 

        Integer day;  

        for(Opportunity opp : opportunityMap.values()){ 

            if(Trigger.isInsert){ 

                day = system.today().day(); // to get day from dateTime  

            } 

            OpportunityDiscountMapping__c  oppDiscount; // instance for remote setting 

          // Get class name from custom setting  

           oppDiscount = OpportunityDiscountMapping__c.
getValues(opp.Type);   

            Type className = Type.forName(oppDiscount.Value__c);            

            //Dynamically calls apex class 

            OpportunityTypeInterface_AC oppInterface = (OpportunityTypeInterface_AC) 

                                                                                               className.newInstance(); 

            Integer discount = oppInterface.applyDiscount(day); 

            opp.Amount = opp.Amount - (discount*opp.Amount/100); // appply discount 

        } 

    } 

}

Strategy Interface Class 

An interface class defines a method that needs to be implemented by all the concrete strategy classes.

public interface OpportunityTypeInterface_AC{ 

    Integer applyDiscount(Integer day); 

}

Concrete Strategy classes 

A group of class that implements a strategy interface. Each class provides a unique solution for the problem. Based on the input type, the class gets called by the context class at runtime.

Solution 1 

public class Discount1Controller_AC implements OpportunityTypeInterface_AC{ 

   public integer applyDiscount(Integer day){ 

        if(Day<=15){ 

            return 5; // discount percentage of days between 1 to 15 of any months             

        } 

        else{ 

            return 10; // discount percentage for remaining days 

        } 

    } 

}

Solution 2 

public class Type2DiscountController_AC implements OpportunityTypeInterface_AC{ 

 public integer applyDiscount(Integer day){ 

        if(Day<=15){ 

            return 15; // discount percentage of days between 1 to 15 of any months 

        } 

        else{ 

            return 20; // discount percentage of remaining days 

        } 

    } 

}

Here the classes are interchangeable at runtime and provide the solutions which suits the user requirements.

Decorator Pattern 

In some scenario, we need to show the value only in UI level, but not to store that value in Database. Here we have a Decorator Design pattern to solve this kind of problems. This pattern provides the decorator class which wraps the apex class to provide the extended functionality for the sObject.

Consider the problem in which we should display the Product value in INR using Visualforce page, but the actual value is in USD. We are going to use the custom object “products” with “Name”(Text) and “Amount”(Currency in dollar) field.

Apex Class 

public class ConvertCurrency_AC{ 

    public List<ProductDecorators_AC> ConvertedCurrency{set; get;} 

    public ConvertCurrency_AC(){ 

        //get all the products and its amount. 

        List<Products__c> productDetail = [SELECT Name, Amount__c  FROM Products__c]; 

        if(productDetail .size() > 0){ 

            ConvertedCurrency = new List<ProductDecorators_AC>(); 

            for(Products__c product: productDetail ){ 

                //passing parameter to the decorator class to get the converted currency. 

                ConvertedCurrency.add(new ProductDecorators_AC(product)); 

            } 

        } 

    } 

}

The following class shows how the Products__c object is wrapped. This object does not have the field to show the value in INR. The Decorator class is used to display the USD in INR.

Decorator Class 

public class ProductDecorators_AC{ 

    //assume the INR value per dollar as 64.78  

    private Double DOLLAR_INR = 64.78; 

    public String ProductName{get;set;} 

    public Double productInDollar {get;set;} 

    public Double productInInr {get;set;} 

    public ProductDecorators_AC(Products__c product){ 

        ProductName = product.Name; 

        productInDollar = product.Amount__c; 

        //converting USD to INR 

        productInInr = productInDollar * DOLLAR_INR; 

    } 

}

VisualForce Page 

<apex:page controller="ConvertedAmount"> 

    <apex:form > 

        <apex:pageBlock title="Product Details"> 

            <apex:pageBlockTable value="{!ConvertedCurrency}" var="convertAmount"> 

                <apex:column headerValue="Product Name" value="{!convertAmount.ProductName}"/> 

                                <apex:column headerValue="Amount in Dollar" value="{!convertAmount.productInDollar}"/> 

                <apex:column headerValue="Amount in Inr" value="{!convertAmount.productInInr}"/> 

            </apex:pageBlockTable> 

        </apex:pageBlock> 

    </apex:form> 

</apex:page>

Output:

The VisualForce page displays the value product Name and Amount from the database. It also displays the converted currency value in INR.

Bulk State Transition 

The Bulk state transition pattern is used to perform bulk actions in Apex based on DML actions. It improves the code reusability and only the record which meets the criteria is allowed to be processed.

For example, consider the following code.

Trigger 

trigger CaseTrigger_AT on Case (after insert, after update) { 

    for(Case newcase : Trigger.new){ 

        if(newcase.Status == 'new'){ 

            Task tsk = new task(); 

            tsk.WhatId = cs.Id; 

            tsk.Subject = 'New Task'; 

            tsk.Priority = 'High'; 

            tsk.Status = 'Not Started'; 

            insert tsk; 

        } 

    } 

}

The above code has the following issue.

  • It is not based on DML type. Whenever the record gets updated without any change in the status of the case, the trigger gets executed unnecessarily.
  • There is no bulk handling. The code exceeds the DML governor limit per transaction when it is used for bulk transaction.
  • The code is not reusable.

The bulk state transition requires a trigger that allows only the eligible record that have changed its state and a utility class to implement the logic. The trigger needs to be framed by its events and the DML type. The following examples show the bulk state transition.

Trigger

trigger CaseTrigger_AT on Case (after insert, after update) { 

    if (Trigger.isAfter && (Trigger.isInsert || Trigger.isUpdate)) { 

        List<case> newCaseList = new List<case>(); 

        for (case newcase : trigger.new) { 

            // check the current and prie state for trigger update  

            if (newcase.Status == 'new' && (trigger.isInsert ||  

                                       (trigger.isUpdate && 

                                        trigger.oldMap.get(newcase.id).Status != 'new'))) 

                newCaseList.add(newcase); 

        } 

        if (!newCaseList.isEmpty()) { 

            TaskCreation_AC task = new TaskCreation_AC(); 

            task.createTask(newCaseList);//parameter passing to the TaskCreation_AC class 

        }   

    } 

}

Class 

public class TaskCreation_AC { 

    List<Task> taskList = new List<Task>(); 

    public void createTask(List<case> caseList){ 

        for(Case newcase : caseList){ 

            Task tsk = new task(); 

            tsk.WhatId = newcase.Id; 

            tsk.Subject = 'New Task'; 

            tsk.Priority = 'High'; 

            tsk.Status = 'Not Started'; 

            taskList.add(tsk); 

        } 

        if(!taskList.isEmpty()){ 

            insert taskList;    

        } 

    } 

}

In the above mentioned code the trigger passes the records which meets the condition to the utility class. The trigger is framed with trigger events and the DML type. The utility class can be reused in future.

Conclusion 

We have implemented the singleton, strategy, decorator and Bulk State Transition with the scenario we faced. There are other patterns are available like Facade pattern and Composite Design Pattern. We should find the pattern which suits our problem and implement it to make our code optimizable, maintainable and reusable.

About MST

At MST Solutions our cornerstone is to adapt, engage and create solutions which guarantee the success of our clients. The talent of our team and experiences in varied business verticals gives us an advantage over other competitors.

Recent Articles

Session Based Permission Sets

A Permission Set is a collection of settings and permissions that give access to users to various tools and functions. The settings and permissions in Permission Sets are additionally found in profiles, but permission sets broaden users’ functional access without altering their profiles.

Read Article »

Work with us.

Our people aren’t just employees, they are key to the success of our business. We recognize the strengths of each individual and allow them time and resources to further develop those skills, crafting a culture of leaders who are passionate about where they are going within our organization.