In the previous post on Picklist Validation, I briefly mentioned my disappointment in not being able to devise a more effective pattern for creating singletons—the optimal method for accessing constant values such as those in a picklist:
Expressing my dislike for crafting singleton property accessors, I’ve invested more hours than I’d like to admit attempting to address this challenge through various abstract class hacks, none of which has proven successful. However, that’s the nature of progress—encountering failures is an inherent part of the process.
Over the following weeks, a significant portion of my time was dedicated to jotting down thoughts on the “problem” initially introduced in the Idiomatic Apex post:
- The traditional get/set method for creating singletons involves nine lines of code. NINE. That’s excessively lengthy.
- Due to the static nature of accessing singletons (to avoid initializing new objects each time they are called), there exists an inherent disparity between the ideal object-oriented solution (which would require the usage of the “this” keyword, strictly prohibited in a static context) and our available options.
- While static variables can be set in constructors, the question remains whether that alone would be sufficient.
Getting Some Singleton Inspiration
Last night, while winding down, the situation finally resolved itself in a burst of inspiration. Messily implemented first in my notebook (my handwritten braces get progressively worse), I finally figured out how to resolve the static nature of singletons with an object-oriented approach:
public abstract class Singleton { private static final Map<Type, Singleton> typeToSingleton = new Map<Type, Singleton>(); public static Singleton getSingleton(Type type) { if(typeToSingleton.containsKey(type) == false) { typeToSingleton.put(type, (Singleton)type.newInstance()); } return typeToSingleton.get(type); } }
Returning to the Picklist class discussed in the previous post, the foundational class merely required an extension of Singleton, enabling subclasses to utilize the type:
public abstract class Picklist extends Singleton { // ...etc } public class AccountIndustries extends Picklist { public AccountIndustries() { super(Account.Industry); } // the newer invocation // note that because there isn't a "set" // method, if I wasn't going for mobile // friendly code, this could be reduced to a single line public static AccountIndustries Current { get { return (AccountIndustries)Singleton.getSingleton(AccountIndustries.class); } } // the more idiomatic singleton, for testing purposes public static AccountIndustries Instance { get { if(Instance == null) { Instance = new AccountIndustries(); } return Instance; } private set; } public String AGRICULTURE { get { return this.getValue('Agriculture'); }} public String APPAREL { get { return this.getValue('Apparel'); }} }
Of course, it’s not enough to simply have code that compiles — is it performant? Let’s do some simple iteration to stress test this new Singleton pattern:
@IsTest private class SingletonStressTests { @IsTest static void it_should_establish_a_baseline_iteration_time() { runTest(null); } @IsTest static void it_should_use_idiomatic_singleton() { runTest(new TestIdiomaticSingleton()); } @IsTest static void it_should_use_new_singleton() { runTest(new TestNewSingleton()); } static void runTest(TestFunction function) { for(Integer index = 0; index < 10000; index++) { if(function != null) { function.call(); } } } private abstract class TestFunction { public abstract void call(); } private class TestIdiomaticSingleton extends TestFunction { public override void call() { System.debug(AccountIndustries.Instance.AGRICULTURE); } } private class TestNewSingleton extends TestFunction { public override void call() { System.debug(AccountIndustries.Current.AGRICULTURE); } } }
Testing The Initial Result
Woof. Initial results were not promising:
TEST NAME | OUTCOME | RUNTIME (MS) |
---|---|---|
SingletonStressTests.itShouldEstablishABaselineIterationTime | Pass | 23 |
SingletonStressTests.itShouldUseIdiomaticSingleton | Pass | 240 |
SingletonStressTests.itShouldUseNewSingleton | Pass | 1160 |
Initially, I pondered whether the slowdown was attributed to the dynamic Type.newInstance or, perhaps, the utilization of the internal Map within the Singleton class. While I expected some degree of slowdown or overhead with this more intricate setup, I did not anticipate that the new method would be six times slower. While such slowdown might be inconsequential for certain applications, that doesn’t align with the Joys Of Apex philosophy. Although excited about the prospect of saving 8 lines of code through the use of this new singleton one-liner, I was not inclined to recommend it to my clients if it entailed such a substantial performance hit.
I experimented with various adjustments, including eliminating the map and passing an actual instance of the class to the getSingleton function (in this case, using new AccountIndustries) instead of dynamically instantiating one. However, none of these changes significantly reduced the runtime.
Then, it dawned on me—the property “Current” itself was not being cached. Just for experimentation, let’s switch to the more idiomatic method for instantiating singletons to see if that yields any performance improvement:
public static AccountIndustries Current { get { if(Current == null) { Current = (AccountIndustries)Singleton.getSingleton(AccountIndustries.class); } return Current; } private set; }
The results were fascinating:
TEST NAME | OUTCOME | RUNTIME (MS) |
---|---|---|
SingletonStressTests.itShouldEstablishABaselineIterationTime | Pass | 27 |
SingletonStressTests.itShouldUseIdiomaticSingleton | Pass | 109 |
SingletonStressTests.itShouldUseNewSingleton | Pass | 85 |
Alright, so the pattern itself was not the cause of the performance slowdown, which was great news. However, it wasn’t so great that it still required 9 lines of code to retrieve a singleton instance. Although Apex does have static constructors, they are not suitable for our needs. Was there a way to guarantee that the property was initialized only once without all the boilerplate?
You might be catching on to where this is heading. There is, indeed, one last strategy we can employ—the final keyword. Traditionally used to ensure an object’s dependencies are set only in the constructor, the final keyword is also compatible with static variables, ensuring they are initialized only once.
This approach gives the AccountIndustries object a much leaner appearance:
public class AccountIndustries extends Picklist { private AccountIndustries() { super(Account.Industry); } public static final AccountIndustries Current = (AccountIndustries) Singleton.getSingleton(AccountIndustries.class); // only keeping this property now to re-run the tests public static final AccountIndustries Instance = new AccountIndustries(); public String AGRICULTURE { get { return this.getValue('Agriculture'); }} public String APPAREL { get { return this.getValue('Apparel'); }} // etc, adding constants as is necessary to // represent your picklists values in code // with minimal usage of "magic" strings // and the added benefit of intellisense }
Running the tests again:
TEST NAME | OUTCOME | RUNTIME (MS) |
---|---|---|
SingletonStressTests.itShouldEstablishABaselineIterationTime | Pass | 23 |
SingletonStressTests.itShouldUseIdiomaticSingleton | Pass | 98 |
SingletonStressTests.itShouldUseNewSingleton | Pass | 90 |
Simple and straightforward. I executed the tests numerous times, and consistently, the newer method proved to be a few milliseconds faster than simply caching the instance. One can only speculate that there are some intriguing optimizations in how the compiler constructs the Type.newInstance code, giving it a slight edge over the use of the new keyword.
Singleton Pattern Conclusion
Key Takeaways from Crafting a More Efficient Singleton:
- You can seamlessly blend object-oriented principles with practical implementation.
- The idiomatic method for instantiating singletons involves the judicious use of the final keyword.
It’s worth noting that when using Type.newInstance(), a public zero-argument constructor is required. This aligns with the insights shared in the Factory post and contradicts the more traditional singleton pattern, where the constructor is made private to enforce invocation through the public static variable. This aspect deserves consideration in your object design choices. Additionally, the limitations of the Type class in Apex, which may necessitate elevating test classes to public visibility, can pose challenges in testing. As always, these aspects provide food for thought.
For further exploration of scenarios where the Singleton pattern proves beneficial, delve into how it enhances invocable and static-based code or consider its synergy with the flyweight pattern in the context of Replace Conditional With Polymorphism!
I trust that this post has been informative. Singleton usage is quite prevalent, and having some tricks up your sleeve for its implementation is always advantageous. Until next time!