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.