Sunday, June 14, 2020

Apex Design Patterns in Salesforce


This Blog describes how to leverage common Apex Design Patterns to optimize your code and ensure reusability, maintainability and performance.

Common Design Patterns

The following are a list of design patterns, some of which are standard object-oriented patterns in a Force.com context, and some of which are specific Force.com patterns.
  • Singleton - minimizing object instantiation for improved performance and to mitigate impact of governor limits
  • Strategy - defining a family of algorithms, enscapsulating each one and making them interchangeable and selectable at runtime
  • Decorator - extending the functionality of an sObject in Apex
  • Facade - simplifying the execution of classes with complex interfaces (e.g. web service callouts)
  • Composite - treating a group of objects in a similar manner to a single instance of that object
  • Bulk State Transition - efficiently tracking the change of a field value in a trigger and executing functionality based on this change
Singleton:

The Singleton pattern attempts to solve the issue of repeatedly using an object instance, but only wishing to instantiate it 
once within a single transaction context.

Sample Code:-
trigger AccountTrigger on Account (before insert, before update) {
    for(Account record : Trigger.new){
        // Instantiate the record type using the singleton class
        AccountFooRecordType rt = AccountFooRecordType.getInstance();
        ....
    }
}

public class AccountFooRecordType {
    // private static variable referencing the class
    private static AccountFooRecordType instance = null;
    public String id {get;private set;} // the id of the record type

    // The constructor is private and initializes the id of the record type
    private AccountFooRecordType(){
        id = Account.sObjectType.getDescribe()
            .getRecordTypeInfosByName().get('Foo').getRecordTypeId();
    }
    // a static method that returns the instance of the record type
    public static AccountFooRecordType getInstance(){
        // lazy load the record type - only initialize if it doesn't already exist
        if(instance == null) instance = new AccountFooRecordType();
        return instance;
    }
}

The above code demonstrates the following:
  • The getInstance() static method will only instantiate an instance of the class if it doesn't already exist in a     lazy-initialization manner
  • The constructor and the instance variable for the class is private to make sure that it cannot be instantiated     outside of the getInstance() method
  • The class defines a private, static instance of itself that can only be referenced via the getInstance() static method

Strategy:

Defining a family of algorithms, enscapsulating each one and making them interchangeable and selectable at runtime.

Sample Code:

1. Create an interface.

public interface Strategy {

   Integer doOperation(Integer num1, Integer num2);
   
}

2. Create concrete classes implementing the same interface.

public class OperationAdd implements Strategy {

   public Integer doOperation(Integer num1, Integer num2) {
      return num1 + num2;
   }
   
}

public class OperationSubstract implements Strategy {

   public Integer doOperation(Integer num1, Integer num2) {
      return num1 - num2;
   }
   
}

3. Create Context Class.

public class Context {

   private Strategy strategy;

   public Context(Strategy strategy) {
      this.strategy = strategy;
   }

   public Integer executeStrategy(Integer num1, Integer num2) {
      return strategy.doOperation(num1, num2);
   }
   
}

4. Use the Context to see change in behaviour when it changes its Strategy.

      Context context = new Context(new OperationAdd());
      System.debug('10 + 5 = ' + context.executeStrategy(10, 5));

      context = new Context(new OperationSubstract());
      System.debug('10 - 5 = ' + context.executeStrategy(10, 5));

Decorator: Extending the functionality of an sObject in Apex.

Sample Wrapper Class using Apex in Salesforce

Sample Code:

Visualforce Page:

<apex:page controller="Sample" tabStyle="Account">
<apex:form >
<apex:pageMessages />
    <apex:pageBlock id="pg">
        <apex:pageBlockSection >
            <apex:pageBlockTable value="{!listWrapper}" var="a">
                <apex:column >
                    <apex:inputCheckbox value="{!a.checkBool}"/>
                </apex:column>
                <apex:column value="{!a.acct.Name}"/>
                <apex:column value="{!a.acct.Industry}"/>
            </apex:pageBlockTable>
        </apex:pageBlockSection>
    </apex:pageBlock>
</apex:form>
</apex:page>

Apex Controller:

public class Sample {
    public List<WrapperClass> listWrapper {get;set;}
 
    public Sample() {
        List<Account> listAcct = [SELECT Name, Industry FROM Account LIMIT 1000];
     
        if(listAcct.size() > 0) {
            listWrapper = new List<WrapperClass>();
         
            for(Account a : listAcct) {
                listWrapper.add(new WrapperClass(a));
            }
        }
    }
 
    public class WrapperClass {
        public Boolean checkBool {get;set;}
        public Account acct {get;set;}
        public WrapperClass(Account acct) {
            this.acct = acct;
        }      
    }
}




Facade:
Simplifying the execution of classes with complex interfaces (e.g. web service callouts).

Sample Code:

/* Complex parts */

class CPU {
    public void freeze() { ... }
    public void jump(long position) { ... }
    public void execute() { ... }
}

class HardDrive {
    public byte[] read(long lba, int size) { ... }
}

class Memory {
    public void load(long position, byte[] data) { ... }
}

/* Facade */

class ComputerFacade {
    private CPU processor;
    private Memory ram;
    private HardDrive hd;

    public ComputerFacade() {
        this.processor = new CPU();
        this.ram = new Memory();
        this.hd = new HardDrive();
    }

    public void start() {
        processor.freeze();
        ram.load(BOOT_ADDRESS, hd.read(BOOT_SECTOR, SECTOR_SIZE));
        processor.jump(BOOT_ADDRESS);
        processor.execute();
    }
}

/* Client */

class You {
    public static void main(String[] args) {
        ComputerFacade computer = new ComputerFacade();
        computer.start();
    }
}

Composite:
Treating a group of objects in a similar manner to a single instance of that object. Composite pattern is used where we need to treat a group of objects in similar way as a single object. Composite pattern composes objects in term of a tree structure to represent part as well as whole hierarchy. This type of design pattern comes under structural pattern as this pattern creates a tree structure of group of objects.

Sample Code:

Apex Class:

public class Employee {

   private String name;
   private String dept;
   private Integer salary;
   private List<Employee> subordinates;

   // constructor
   public Employee(String name,String dept, Integer sal) {
      this.name = name;
      this.dept = dept;
      this.salary = sal;
      subordinates = new List < Employee >();
   }

   public void add(Employee e) {
      subordinates.add(e);
   }

   public List<Employee> getSubordinates() {
     return subordinates;
   }

}

Execute the below code to set the hierarchy and view the debug log.
      
      Employee CEO = new Employee('John','CEO', 30000);

      Employee headSales = new Employee('Robert','Head Sales', 20000);

      Employee headMarketing = new Employee('Michel','Head Marketing', 20000);


      Employee clerk1 = new Employee('Laura','Marketing', 10000);
      Employee clerk2 = new Employee('Bob','Marketing', 10000);

      Employee salesExecutive1 = new Employee('Richard','Sales', 10000);
      Employee salesExecutive2 = new Employee('Rob','Sales', 10000);

      CEO.add(headSales);
      CEO.add(headMarketing);

      headSales.add(salesExecutive1);
      headSales.add(salesExecutive2);

      headMarketing.add(clerk1);
      headMarketing.add(clerk2);

      //print all employees of the organization
      System.debug(CEO); 
      
      for ( Employee headEmployee : CEO.getSubordinates() ) {
         System.debug(headEmployee);
         
         for ( Employee employee : headEmployee.getSubordinates() ) {
            System.debug(employee);
         }

      }


Related Link

No comments: