The singleton pattern

The singleton design pattern restricts the instantiation of a class to only one object.

This is a useful design pattern in Apex and is frequently used. As discussed in the previous chapter, Salesforce has various governor limits; for example, a number of SOQL queries, a number of query rows returned, and a number of DML operations that can be performed in a single request.

Using the singleton design pattern, we can make sure that utility classes are instantiated only once, which can help in avoiding governor limits.

The sales division of a call center receives calls either from a customer or broker who is interested in the product. If a call comes directly from a customer, then the call center agents need to create a new opportunity record with all the required information. Alternatively, if a call comes from a broker, then the call center agents need to create an opportunity record and then record it for a broker as well. Brokers are eligible for commission if they sell company products to new customers. The company also has area-specific offices and would like to autopopulate it while creating or updating the opportunity and broker.

Salesforce developers of the call center came up with the following database structure:

The singleton pattern

Developers created the following utility class to get the area office saved in the Salesforce object on the basis of its state and city:

/**  
 * This class is used to query all existing offices and utility methods 
 * to return Office record on basis of state and city 
 * */ 
public class AreaOfficeUtil { 
 
    private List<Area_Office__c> lstAllOffices; 
 
    private Map<String,Area_Office__c> mpAllOffices ; 
     
    public AreaOfficeUtil(){ 
        //Even though there are no more than 1500 records, but  
        //on safer side, Limit 2000 
         
        lstAllOffices = [Select  
                           Name, 
                           City__c, 
                           State__c, 
                           Manager__c, 
                           Head_Count__c 
                        FROM 
                           Area_Office__c 
                        LIMIT 2000 
                        ]; 
         
       mpAllOffices = new Map<String,Area_Office__c>(); 
         
        for(Area_Office__c ofc : lstAllOffices){ 
            mpAllOffices.put(ofc.State__c+'-'+ofc.City__c,ofc); 
        } 
         
    } 
     
     
    //  Get "Area_Office__c" on basis of state and city  
    public Area_Office__c getOffice(String state, String city){ 
        return mpAllOffices.get(state+'-'+city); 
    } 
     
} 

In the preceding class, SOQL is executed in a constructor, and a map of offices is created with state-city as keys so that in all subsequent requests, developers can save SOQL.

Business users wanted to check that whenever an opportunity is created or updated, a call center agent should enter a proper area office, then it needs to be autopopulated. One more requirement was given that if an opportunity is marked as closed won, then the related broker qualifies for commission.

Developers came up with the following trigger on an opportunity:

trigger OpportunityTrigger on Opportunity (before insert, before update, after update) { 
    
    // Instance of utility class to perform Area office  
    // related operations 
    AreaOfficeUtil util = new AreaOfficeUtil(); 
     
    //This set will hold Id of all closed won Opportunities 
    Set<Id> setClosedWonOpp = new Set<Id>(); 
     
    for(Opportunity opp : Trigger.New) 
    { 
        //Make sure Opportunity should not be tried to be  
        // updated in after trigger, else we will get  
        // "record read only" error 
 
        if(!Trigger.isAfter) 
        { 
            //If Area Office field is not populated and  
            //state, city is valued 
 
            if(opp.Area_Office__c == null && opp.State__c != null && opp.City__c != null ) 
            { 
                Area_Office__c ofc = util.getOffice(opp.State__c, opp.City__c); 
                if(ofc != null) 
                { 
                    opp.Area_Office__c = ofc.Id; 
                }  
            } 
        } 
         
         
        //If Opportunity is closed won then update all related brokers qualified for commission 
 
        if(opp.StageName == 'Closed Won' && Trigger.isAfter && Trigger.isUpdate){ 
            setClosedWonOpp.add(opp.Id); 
        } 
         
    } 
     
    //Save SOQL if there set is empty 
    if(!setClosedWonOpp.isEmpty()) 
    { 
        List<Broker__c> lstBrokersToUpdate = [ 
                  Select  
                            Id, 
                            Commision__c, 
                            Opportunity__c  
                        FROM  
                           Broker__c  
                        WHERE 
                           Opportunity__c IN:setClosedWonOpp ]; 
        for(Broker__c b : lstBrokersToUpdate) 
        { 
            b.commission__c = true; 
        } 
         
        //update all qualified brokers at once 
        Database.update(lstBrokersToUpdate,false);  
    } 
      
} 

After a few days, the same requirement to autopopulate a broker's area office was given to the developers, and they came up with the following simple trigger:

trigger BrokerTrigger on Broker__c (before insert, before update) { 
 
    // Instance of utility class to perform Area office  
    // related operations 
    AreaOfficeUtil util = new AreaOfficeUtil(); 
     
    for(Broker__c b : Trigger.New) 
    { 
        if(b.Area_Office__c == null && Trigger.isBefore  
              && b.State__c != null && b.City__c != null) 
        { 
 
            Area_Office__c ofc = util.getOffice(b.State__c, b.City__c); 
 
            if(ofc != null) 
            { 
                b.Area_Office__c = ofc.Id; 
            }  
        } 
    } 
} 

The code was working fine, as was expected by business users of the call center. Like all software development projects, a day came when the Salesforce technical architect reviewed the code and informed them that the code was not written efficiently. Everyone was surprised as the developers had already tried their best to make the code modular by delegating access of area office to the utility class. The Salesforce technical architect informed them that if an opportunity has one or more brokers, and when an opportunity is moved to closed won, a total of five SOQL queries will be executed and this could be reduced to just two SOQLs.

The Salesforce architect asked the team to go through the order of execution of a trigger. Developers understood the issue. The following image shows the order of execution:

The singleton pattern

The preceding image does not provide a detailed information of the execution order in Salesforce. To read more about this, refer to https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_triggers_order_of_execution.htm.

In the preceding image, we can see that a constructor of the AreaOfficeUtil class is using one SOQL query. For the Opportunity record, before and after the event of a trigger creates one instance each of AreaOfficeUtil. Also, as the Opportunity trigger updates the broker record, it results in the invocation of a broker trigger. A broker trigger also creates one instance each of AreaOfficeUtil before and after events. AreaOfficeUtil is instantiated a total of four times, resulting in four SOQL queries.

This is just one example; however, there are many similar situations in the project where the same object is used between different triggers and fires the same SOQL multiple times. As discussed in Chapter 1, An Introduction to Apex Design Pattern, Salesforce is a multi-tenant platform, and it forces many governor limits to avoid the monopoly of resources. One of the most common governor limits that Apex developers should be wary of is the total number of SOQL queries used in a single user request.

One of the Salesforce developers had an idea and changed the AreaOfficeUtil class, as shown in the following code:

/**  
 * This class is used to query all existing offices and utility methods 
 * to return Office record on basis of state and city 
 * */ 
public class AreaOfficeUtil { 
 
   private List<Area_Office__c> lstAllOffices; 
 
    private Map<String,Area_Office__c> mpAllOffices ; 
     
    //Static variable to hold instance of same class 
    //and avoid creation of object again 
 
    private static AreaOfficeUtil selfInstance = null; 
     
    //Static method to check if its already instantiated, 
    //if not then call private constructor and return 
    //object 
 
    public static AreaOfficeUtil getInstance(){ 

 
        if(selfInstance == null) 
            selfInstance = new AreaOfficeUtil(); 
 
        return selfInstance; 
    } 
     
    //Private constructor to make sure no one 
    //can instantiate this class 
 
    private AreaOfficeUtil(){ 
         
        //Even though there are no more than 1500 records, but  
        //on safer side, Limit 2000 
         
        lstAllOffices = [Select  
                           Name, 
                           City__c, 
                           State__c, 
                           Manager__c, 
                           Head_Count__c 
                        FROM 
                           Area_Office__c 
                        LIMIT 2000 
                        ]; 
         
       mpAllOffices = new Map<String,Area_Office__c>(); 
         
        for(Area_Office__c ofc : lstAllOffices){ 
 
            mpAllOffices.put(ofc.State__c+'-'+ofc.City__c,ofc); 
 
        } 
         
    } 
         
    // Get "Area_Office__c" on basis of state and city       
    public Area_Office__c getOffice(String state, String city){ 
 
        return mpAllOffices.get(state+'-'+city); 
 
    } 
     
} 

In the Opportunity and Broker__c triggers, we need to change the following line of code:

AreaOfficeUtil util = new AreaOfficeUtil(); 

This line is changed to:

AreaOfficeUtil util = AreaOfficeUtil.getInstance(); 

The preceding code provides a good usage of private constructors.

Note

Private constructors

Private constructors are special types of constructors. They are used to restrict other classes to instantiate objects, and the only way to instantiate is inside a class itself. In most scenarios, the Static method is exposed to instantiate it from an external class.

The idea was that the Opportunity trigger and Broker trigger were executed in a single request. So, the AreaOfficeUtil class will be instantiated only once during the entire user request cycle. This will result in only one SOQL query getting invoked. The Salesforce static variable solved this purpose.

Tip

Unlike a static variable in Java where it lasts until the JVM runs, the Salesforce static variable scope exists only in one transaction/request.

The following image shows a class diagram of the singleton pattern used in the previous example:

The singleton pattern

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset