- Design Patterns In Dart – Part 2
- Design Patterns In Dart
Post Stastics
- This post has 937 words.
- Estimated read time is 4.46 minute(s).
The Singleton Pattern
From time to time you'll find you need to ensure that your code creates only a single instance of a class. This often occurs in hardware drivers or certain OS sub-systems. It's not a feature you need often but, when you need it, you really need it! The singleton pattern is one of those patterns that often gets a bad reputation because it is so often overused. That is partly because people have misconceptions about when they truly need it, and how easy it is to use.
If you find yourself needing to create a singleton class the first thing you should do is STOP! Then ask yourself if you truly only need one instance of the object you are creating? If so why? Will the world come to an end if more than one instance exists? Will there be some kind of conflict? Is there a better way to handle that conflict? Because once you go singleton, it's very hard to go back! Make sure it's the choice you want to make. If it is, then move on.
![Singleton UML Class Diagram](https://www.coderancher.us/wp-content/uploads/2021/04/Singleton.png)
Below is a typical singleton class named Singleton. The trick to this class is that it contains a private static instance variable "_instance". When we need an instance of this class we call getInstance. This method first checks to see if _instance is null and if so, makes a call to the private named constructor _create() to create an instance of the class and assigns that instance to _instance. Any further calls to the getInstance method will now cause the if clause in getInstance to fail and the existing _instance value stored in _instance will be returned. So there is no way to create another instance of Singleton once an instance has been created.
class Singleton { static Singleton? _instance; Singleton._create(); static Singleton? getInstance() { if (_instance == null) { _instance = Singleton._create(); } return _instance; } }
We can use the class above like so:
final singleton = Singleton.getInstance();
Some might argue that the above code is not quite, idiomatic Dart code. Dart does have some features that can make our code a bit easier to read. For completeness here is a more idiomatic version of the Singleton class.
class Singleton { static Singleton? _instance; Singleton._create(); static get instance { _instance = (_instance == null ? Singleton._create(): _instance); return _instance; } }
Perhaps a slight improvement. I think the actual improvement comes when you call this code as the parentheses are not required for the instance getter.
final singleton = Singleton.instance;
Dart does however have an even better solution. We can use the Dart Factory keyword and use a factory to generate a Singleton instance for us.
class Singleton { static Singleton? _instance; Singleton._create() { _instance = this; } factory Singleton() => _instance ?? Singleton._create(); }
Here the public default constructor is a factory. A factory constructor can be used in cases where some special processing must be done to construct the expected class. Often the factories are used to generate objects of a specific type while having a common base type. A good example is a shape generator. You might have a base class of shape that includes a factory constructor that generates objects of square or circle types depending on the parameters passed into the constructor. In our case, we use the factory to ensure we only create a single instance of the Singleton class and always return that single instance. We do this by using the ?? null aware operator which returns the expression on its left unless that expression's value is null, in which case it returns the expression on its right.
Using this class is the easiest of all the examples as the factory constructor is the default object constructor so the call doesn't even reveal that we are doing anything out of the ordinary.
final singleton = Singleton();
When to Use Singletons
Now that we know how to create a singleton, you might ask when would we want to use one? Singletons come in handy when you need a single instance of an object. You will see this in some database drivers. Hardware drivers also offer a good example of this. Anytime you have a resource that should only have one instance in existence you have an example of a singleton.
One acceptable example of a "convenience" use of a Singleton class is logging. In this case, a Singleton can be used instead of a single instance of a class because a logging class usually needs to be used over and over again by every class in a project. If every class uses this logging class, dependency injection becomes cumbersome. One reason the Singleton use is allowed in logging this way is because of the one-way information flow, from the application code to the logger. Great debates have raged over the perils and virtues of Singletons for at least 3 decades now. If you would like to learn more I'll include some links to additional reading below.
Of all the design patterns the Singleton pattern may be the most abused. This is because it is both the easiest to understand and to implement. In Dart, it becomes even easier to implement and the danger of misuse becomes even greater. However, like fire, this pattern has its place and there are times that it is completely appropriate. When it is truly needed, it is powerful! Just be sure it is the right tool for the job at hand. Do you really only ever want one instance of your object in existence? Because this will often be a decision you can't come back from!