In Dart there is an interesting language feature called ‘Factory Constructors’, which effectively allows you to override the default behaviour when using the new keyword – instead of always creating a new instance the factory constructor is merely required to return an instance of the class, the difference is important.
Factory constructors allow you to implement a number of techniques and patterns without altering code that consumes your class. For instance,
- Singleton pattern which we will look at more closely.
- Object pooling, a useful technique for reducing the amount of allocations (and its associated allocation cost and consequent GC pressure) in performance critical applications.
- Flyweight pattern which is already discussed in more detail in this Idiomatic Dart article.
These are just 3 use cases that I can think of off the top of my head, please feel free to suggest any more that I have missed.
Problems with common Singleton pattern implementations
In other languages (well, the ones that I’m familiar with anyway!), in order to implement the Singleton pattern you have to ensure that the class’s constructor is not exposed publicly and that access to the singleton instance is done via a static Singleton property. Revered C#/Java developer Jon Skeet has a very good article on the various solutions one might adopt to implement the singleton pattern in C#.
These implementations require code that consumes your class to be aware of its implementation of the singleton pattern and create a vast blast radius throughout your application should you one day decide that the singleton pattern is no longer necessary/applicable.
For instance, if assumptions in your application change drastically (and they often do..) and you need to switch to the flyweight or another pattern instead to cater for changing requirements and/or assumptions.
Unintentional tight coupling
In the case of C# (where static members are not allowed on interfaces and abstract classes are un-constructible) the standard singleton pattern also create tight coupling to a concrete implementation where it’s seldom necessary.
You can, to some degree, work around this issue of tight coupling by introducing an IOC container as middle man between your class and its consumers, most IOC containers provide some mechanism for controlling object lifespans (transient, singleton, pooled, etc.). However, you now have tight coupling to the IOC container instead…
Singleton pattern with Factory constructors
You can implement the singleton pattern using factory constructors like this:
the key thing here is that any consuming code is completely oblivious to the fact that we have just implemented the singleton pattern. If we were to continue our mind later or forced to adopt a different pattern because of changing requirement, there will be trivial or no change on all the consuming code!