Apex offers the flexibility to create robust and scalable custom solutions in Salesforce. Understanding and applying design patterns not only enhances your day-to-day development skills but also prepares you to excel in Salesforce Developer interviews.

Here are some of the most commonly used design patterns in Apex:

1. Singleton Pattern

This pattern ensures that a class has only one instance while providing a global point of access to it. It’s useful for improving performance and minimizing governor limit impact.

Example: Lazy Initialization Singleton

public class RecordTypeManager {

    private static RecordTypeManager instance = null;

    private String recordTypeId;

    private RecordTypeManager() {

        recordTypeId = Account.SObjectType.getDescribe()

                          .getRecordTypeInfosByName()

                          .get(‘SpecialType’)

                          .getRecordTypeId();

    }

    public static RecordTypeManager getInstance() {

        if (instance == null) {

            instance = new RecordTypeManager();

        }

        return instance;

    }

    public String getRecordTypeId() {

        return recordTypeId;

    }

}

Use Case: Access a record type ID across triggers without querying multiple times.

2. Strategy Pattern

The strategy pattern allows for selecting an algorithm dynamically at runtime. It promotes flexibility and decoupling by enabling different implementations of the same behavior.

Example: Payment Gateway Selector

public interface PaymentProcessor {

    void processPayment(Decimal amount);

}

public class StripeProcessor implements PaymentProcessor {

    public void processPayment(Decimal amount) {

        System.debug(‘Processing $’ + amount + ‘ using Stripe’);

    }

}

public class PaypalProcessor implements PaymentProcessor {

    public void processPayment(Decimal amount) {

        System.debug(‘Processing $’ + amount + ‘ using PayPal’);

    }

}

public class PaymentService {

    private PaymentProcessor processor;

    public PaymentService(PaymentProcessor processor) {

        this.processor = processor;

    }

    public void executePayment(Decimal amount) {

        processor.processPayment(amount);

    }

}

Usage:

PaymentService payment = new PaymentService(new StripeProcessor());

payment.executePayment(100.00); // Switch to PayPal by passing a different processor

3. Decorator Pattern

The decorator pattern dynamically adds behaviors to objects without altering their structure. It is especially useful in enhancing sObject functionality for temporary use cases.

Example: Enhancing Account with Temporary Fields

public class AccountDecorator {

    public Account account;

    public Boolean isSelected { get; set; }

    public Decimal projectedRevenue { get; set; }

    public AccountDecorator(Account account) {

        this.account = account;

        this.isSelected = false;

        this.projectedRevenue = 0;

    }

}

Use Case: Add temporary fields like checkboxes or calculated values during Visualforce page processing.

4. Facade Pattern

The facade pattern simplifies the interface to complex systems. This is particularly helpful for managing multiple service classes or API integrations.

Example: Simplified Web Service Callout

public class WeatherServiceFacade {

    public static String getWeather(String city) {

        HttpRequest req = new HttpRequest();

        req.setEndpoint(‘https://api.weather.com/’ + city);

        req.setMethod(‘GET’);

        Http http = new Http();

        HttpResponse res = http.send(req);

        return res.getBody();

    }

}

Use Case: Provide a simple method for making external callouts instead of repeating complex logic.

5. Composite Pattern

The composite pattern represents a group of objects as a single entity, useful for processing complex hierarchies or nested structures.

Example: Expression Evaluation

public interface Expression {

    Boolean evaluate();

}

public class AndExpression implements Expression {

    private Expression exp1, exp2;

    public AndExpression(Expression exp1, Expression exp2) {

        this.exp1 = exp1;

        this.exp2 = exp2;

    }

    public Boolean evaluate() {

        return exp1.evaluate() && exp2.evaluate();

    }

}

public class ValueExpression implements Expression {

    private Boolean value;

    public ValueExpression(Boolean value) {

        this.value = value;

    }

    public Boolean evaluate() {

        return value;

    }

}

Use Case: Evaluate logical expressions like (A AND B) OR C.

6. Bulk State Transition Pattern

This pattern helps process bulk changes in record states efficiently, reducing governor limit issues in triggers.

Example: Create Orders from Closed Opportunities

trigger OpportunityTrigger on Opportunity (after update) {

    List<Opportunity> closedOpportunities = new List<Opportunity>();

    for (Opportunity opp : Trigger.new) {

        if (opp.IsClosed && !Trigger.oldMap.get(opp.Id).IsClosed) {

            closedOpportunities.add(opp);

        }

    }

    if (!closedOpportunities.isEmpty()) {

        OrderService.createOrders(closedOpportunities);

    }

}

public class OrderService {

    public static void createOrders(List<Opportunity> opportunities) {

        List<Order__c> orders = new List<Order__c>();

        for (Opportunity opp : opportunities) {

            orders.add(new Order__c(Opportunity__c = opp.Id));

        }

        insert orders;

    }

}

Use Case: Automatically generate related records based on state transitions.

Conclusion

Apex design patterns simplify coding, improve performance, and enhance maintainability. By applying these principles, you can create scalable, efficient solutions in Salesforce that are easy to extend and understand.

Leave a comment