20

I've been studying Design Patterns. A Singleton pattern is described as "minimizing object instantiation for improved performance and to mitigate impact of governor limits."

In the Technical Library doc Apex Design Patterns under Singleton, it says the following.

Common uses for this pattern include:

  • Global variables - whilst Apex does not support global variables across execution contexts, this pattern allows you to create an object instance that will only ever be instantiated once within an execution context.

  • Limiting Impact of Governor Limits - certain system objects and methods, such as Apex Describes, are subject to governor limits. The Singleton pattern allows repeated reference to these without breaching governor limits.

  • As an implementation to other patterns - other design patterns, such as Facade, are often implemented as Singletons.

However, it's most common use is to create an object instance that's instantiated only once for the lifetime of that execution context.

In the implementation, it goes on to give an example of a trigger that calls a class that uses a describe call to instantiate a record type for an Account.

The Question:

Now that Governor Limits no longer exist on describe calls (describe call limits were 100 per execution context when article was written), other than speed from not having to make the additional calls, what will be the primary benefits in the context of Apex for using the Singleton Design pattern?

I can see where having a query or describe call having to only run once and then essentially being cached in a variable offers a lot of potential for speed advantages and reduced memory when processing code, but clearly the specific governor limits that previously were cited as drivers for using this pattern in an APEX context no longer exist on the platform.

Are there other governor limits that still exist where the Singleton pattern is of particular value or other use cases where it's especially advantageous or frequently used?

For reference:

The Trigger

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();
        ....
    }
}

The Singleton Class

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;
    }
}
  • I feel like your question assumes as a foregone conclusion that describes are no longer a great use case for this pattern. So you're not really asking about describes at all! – Adrian Larson Jul 21 '16 at 23:58
  • @AdrianLarson I simply restated what's in the documentation as reasons to use this pattern while observing that those conditions not longer appear to exist. I noted that it appeared to me that using the pattern could still be advantageous in terms of speed. I wanted to know if my observations were accurate and was asking what other drivers might exist for continuing to utilize the pattern with Apex. I don't think I drew any conclusions. Instead, I asked for clarification on best use cases in light of the governor limits having changed. – crmprogdev Jul 22 '16 at 11:57
14

Singleton designs are never as efficient as static variables, which are not as efficient as local (method) variables. Do feel free to use static variables to cache common queries, but do not use them to cache unmetered results, such as describe calls. Depending on the object being described, it can actually increase the CPU time used.

As an example, I give you this:

public class AccountSObjectType {
    static DescribeSObjectResult instance;
    public static DescribeSObjectResult getDescribe() {
        if(instance == null) instance = Account.SObjectType.getDescribe();
        return instance;
    }
}

In my org, I ran this for a baseline:

Long start = DateTime.now().getTime();
for(Integer i = 0; i < 100000; i++) {
    DescribeSObjectResult recordTypeId = Account.SObjectType.getDescribe();
}
Long stop = DateTime.now().getTime();
System.assert(false, stop-start);

The result was about 1900 milliseconds. I figured that using the singleton should improve my performance, right?

Long start = DateTime.now().getTime();
for(Integer i = 0; i < 100000; i++) {
    DescribeSObjectResult recordTypeId = AccountSObjectType.getDescribe();
}
Long stop = DateTime.now().getTime();
System.assert(false, stop-start);

The first run result of that was about 3800 ms (subsequent runs got no lower than about 2400 ms). That's right. 50-100% more CPU time than the direct call. The Singleton actually failed to outperform a describe called directly from a Schema object.

So, use the Singleton as a convenience tool (e.g. gather data all in one place), but do not always expect it to give you a boost in performance.

  • So the basic use case where a Singleton becomes more efficient are cases where the initialization/instantiation of an object would always generate the same result. And the initialization is more expensive than the code required to implement the Singleton. I've mostly used Singletons for caching data from SOQL queries that are used across multiple classes. – abd3721 Jul 28 '16 at 1:52
13

Aside from lower limit consumption, I find another benefit of the Singleton Pattern is that it simplifies serialization of the involved state. For example, you may want to pass this state to Javascript for use in a Single Page Application. Having serializable state made projects involving angular development way easier for me to manage. I guess that's not in the context of Apex but it can be a compelling argument.

Two key Limits still make the Singleton Pattern effective: Limits.getQueries() and Limits.getCpuTime(). In my opinion, this pattern is just a different flavor of lazy-load.

The following is a vanilla example where a Singleton is still useful. If called many times in a loop, caching would save a large amount of CPU Time.

public with sharing class RecordTypeCache
{
    static RecordTypeCache instance;
    public RecordTypeCache getInstance()
    {
        if (instance == null)
            instance = new RecordTypeCache();
        return instance;
    }

    final Map<String, Map<String, RecordType>> cache;
    public RecordTypeCache()
    {
        cache = new Map<String, Map<String, RecordType>>();
        for (RecordType rt : /*get all RecordType data*/)
        {
            if (!cache.containsKey(rt.SObjectType))
                cache.put(rt.SObjectType, new Map<String, RecordType>());
            cache.get(rt.sObjectType).put(rt.DeveloperName, rt);
        }
        return cache;
    }
    public Map<String, RecordType> get(SObjectType schemaType)
    { // Never return a null collection
        String sObjectType = String.valueOf(schemaType);
        return cache.containsKey(sObjectType) ?
            cache.get(sObjectType) : new Map<String, RecordType>();
    }
    public RecordType get(SObjectType sObjectType, String developerName)
    {
        return this.get(sObjectType).get(developerName);
    }
}

If you don't care about preserving state, having an instance at all seems almost foolish, using static properties tends to be much more concise.

public with sharing class RecordTypeCache
{
    static final Map<String, Map<String, RecordType>> cache;
    {
        cache = new Map<String, Map<String, RecordType>>();
        for (RecordType rt : /*get all RecordType data*/)
        {
            if (!cache.containsKey(rt.SObjectType))
                cache.put(rt.SObjectType, new Map<String, RecordType>());
            cache.get(rt.sObjectType).put(rt.DeveloperName, rt);
        }
    }
    public static Map<String, RecordType> get(SObjectType schemaType)
    { // Never return a null collection
        String sObjectType = String.valueOf(schemaType);
        return cache.containsKey(sObjectType) ?
            cache.get(sObjectType) : new Map<String, RecordType>();
    }
    public static RecordType get(SObjectType sObjectType, String developerName)
    {
        return this.get(sObjectType).get(developerName);
    }
}

Though it should be very rare that you ever need to worry about Limits.getHeapSize(), you could go so far as to clear out the instance when you are done using it. This sort of manual "garbage collection" should only be used as a last resort, I would think. But if you have many properties, you could clear out heap faster with a singleton than clearing each property one at a time.

Here is an Execute Anonymous snippet you can use to demonstrate this heap benefit:

class Obj
{
    final List<Integer> collection;
    Obj() { collection = new List<Integer>(); }
}
static Obj instance;
static Obj getInstance()
{
    if (instance == null) instance = new Obj();
    return instance;
}
static void clear() { instance = null; }

for (Integer i = 0; i < 100000; i++)
{
    getInstance().collection.add(i);
}

system.debug(Limits.getHeapSize()); // 1201092
clear();
system.debug(Limits.getHeapSize()); // 1080
  • +1 But I found an instance where a Singleton managed to be beat by repeatedly describing an object. I'm pretty sure I need to investigate further, but it's definitely not the magic bullet it used to be. – sfdcfox Jul 21 '16 at 21:09
  • 1
    I'm with @sfdcfox, unless proven otherwise in one of my use cases, I've switched to doing inline describes; I'll cache SOQL (reference data) but not describes - although this could clearly be org-dependent – cropredy Jul 21 '16 at 21:36
  • 2
    Yes but the question isn't about describes, is it? I feel like the question is about when it is a useful pattern. – Adrian Larson Jul 22 '16 at 0:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.